Noah Bragg 的首次 Stoke Fire 直播
原文由 Michael Lynch 于 發布,訂閱此部落格
過去一年我一直對以太坊很感興趣,尤其是 Base 生態系。問題在於,即使花了好幾個小時研究 Base,我還是不懂 Base 到底是什麼。
每隔幾個月,我就會回去看一下 Base 官網的開發者專區,想看看有沒有適合新手在 Base 上開發的入門路徑,結果看到的似乎都是「這裡有一些針對非常特定主題、彼此零散的教學,如果有問題,就來 Discord 問我們吧」。
所以,當我看到自己在 Twitter 上追蹤的獨立創業者 Noah Bragg 開始直播他在 Base 生態系上打造一款簡單遊戲的過程時,我感到相當興奮。

獨立創業者 Noah Bragg 正在直播他在以太坊區塊鏈上打造遊戲的過程。
我看了 Noah 的第一場直播,並在下方分享我的心得。
遊戲:Stoke Fire
- Stoke Fire 是一款資源管理遊戲,玩家在村莊裡砍柴,為持續燃燒的營火添柴,以保持村莊溫暖。

- 靈感來源
- Noah 小時候玩過的 Age of Empires II。
- 文字網頁遊戲 A Dark Room。
- Manor Lords,一款由單人開發者耗時六年打造的全面戰爭模擬遊戲。
- 遊戲將採免費遊玩,以鼓勵更多人參與。
- 遊戲會將存檔狀態保存在 NFT 中,以方便在不同錢包之間轉移,但 Noah 並不預期人們會像交易藝術品 NFT 那樣去交易這些遊戲存檔 NFT。
- 他選擇在 Base 區塊鏈上開發,是因為他預期受惠於 Coinbase 的投入,消費者會聚集在那裡。
搶先體驗
- Stoke Fire 的搶先體驗可透過 Noah 在 Hypersub 上的 I Must Build 訂閱取得。
- Hypersub 就像是鏈上版的 Patreon。
直播中發生了什麼
- Noah 在遊戲中新增了用木材建造小屋的功能。
- 他開始實作根據村莊可用資源來吸引村民的功能,但最終做到一半就暫停了,因為他已經心力耗盡。
Solidity
Michael 註:Solidity 還是讓我很受不了。在一個正確性與可讀性至關重要的領域,Solidity 卻引入了一大堆不必要的陷阱和地雷。感覺就像他們拚命研究了 C++ 和 JavaScript,只為了把那兩種語言最糟糕的特性全部學起來。
Diamond
- Diamond 是一個用來部署發布後仍可修改的智慧合約的框架。
- 一般來說,以太坊智慧合約一旦部署就無法再更動。
- Diamond 提供了「可升級」的智慧合約,讓你在部署後還能修改它們。
- Diamond 削弱了智慧合約的保證,但對於像 Stoke Fire 這樣需要反覆迭代的專案來說很有幫助。
- 「Facets」(我想)就是智慧合約中可被修改的部分。
Michael 註:管理 facets 看起來很繁瑣,也是 Noah 展示的程式碼中最脆弱的部分。他必須手動宣告好幾個不同的陣列,然後在新增 facets 時手動去索引它們,甚至一度為了讓 facet 數量對上某個藏得很深的宣告中的陣列大小而卡了好一陣子。聊天室的留言說可以用 helper 自動取得 facets,所以或許有更簡單的做法。
Forge
- Noah 使用 Forge 來測試他的智慧合約邏輯。
- Noah 對 Forge 的評價頗為正面。
- 他喜歡 Forge 讓他能用 Solidity 寫測試,因為這樣可以和正式環境的智慧合約共用大量程式碼。
- Forge 讓測試不同的區塊鏈條件變得很容易,例如時間戳記和錢包餘額。
- Noah 有時難以解析 Forge 的輸出,而我個人則覺得它的輸出非常雜亂、難以閱讀。

我覺得 Forge 的測試輸出非常雜亂
- 當 Forge 中的 assert 失敗時,它不會印出造成失敗的行號。在直播的某個片段中,Forge 印出的失敗訊息就只有
5 != 4,開發者得自己去找出原始碼中是哪個 assertion。像這樣失敗時不印出行號的測試框架,我還真是從沒見過。
- 當 Forge 中的 assert 失敗時,它不會印出造成失敗的行號。在直播的某個片段中,Forge 印出的失敗訊息就只有
- 為了要斷言 Noah 的正式程式碼拋出了特定錯誤,他必須在測試程式碼中重新定義該錯誤,這點讓我覺得很奇怪。
Warpcast 有網頁版
- Farcaster 就像是 Twitter 或 Mastodon 的以太坊版本。
- Farcaster 給人的印象是 Warpcast 只有行動版,但看了 Noah 的直播我才發現 Warpcast 有網頁版。
- 我想你還是需要用行動 App 來建立帳號。
直播
- Noah 的直播最高有 250 位觀眾。
- 不過到最後他也不確定這是指同時在線的觀眾數,還是曾經點進來看過直播的累計總人數。
- 大多數觀眾來自 Twitter。
- 他坦言自己對直播已經有點生疏,過程中出現了不少冷場的空白時間。
可以改進的地方
便利的開發腳本
Noah 有一次卡了好幾分鐘,就為了想起如何只執行單一測試,而不是每次都跑完整套測試。在聊天室觀眾的幫忙下,他最後找到了語法:
forge test --match-contract BuildFacetTest -vvvv對於這種不好記的命令列語法,我會建議寫一個便利腳本並放在儲存庫裡。這樣就不用去記在眾多技術堆疊中執行單一測試的不同語法,只要執行像這樣的指令就好:
./dev-scripts/run-single-test BuildFacetTest我在好幾個儲存庫中都是這樣做的。
Noah 已經在使用 make,所以這些也可以改成 make 指令。
讓測試更容易維護
Noah 為建造小屋功能寫的最終整合測試長這樣:
function testBuildHut() public {
vm.prank(USER);
ResourceFacet(address(diamond)).chopWood(1, someWhatRandNum);
vm.warp (1719981068 + 1 days); //set the block time to the future so I can chop wood again
vm.prank(USER);
ResourceFacet(address(diamond)).chopWood(1, someWhatRandNum);
vm.prank(USER);
BuildFacet(address(diamond)).buildHut(1);
Village memory village = VillageFacet(address(diamond)).getVillage(1);
assertEq(village.timeLastChoppedWood, block.timestamp);
assertGe(village.wood, 4); //at minimum 2 wood per chop.
assertLe(village.wood, 12); //at maximun 6 wood per chop.
assertEq(village.huts, 1);
}我覺得這個測試有幾個可以改進的地方。
首先,這個測試依賴了一個讓邏輯變得非常混亂的虛擬亂數產生器(PRNG)。為了測試,Noah 已將 PRNG 設為固定的種子,讓每次測試的隨機數序列都能重複,但即便如此,測試邏輯還是很難看懂。
在直播中,Noah 不得不盲目地在測試中一直增加砍柴次數,看看何時才會通過。他最後乾脆把隨機種子改成另一個值,這樣只要砍兩次就能通過。也正因如此,這個測試無法斷言確切的剩餘木材數量,只能猜測它落在某個範圍內。不實際執行,根本無法驗證這個測試的正確性。
如果是我在寫這段程式,我大概會把砍柴功能 mock 掉,改用一個測試用的實作,讓我能精確定義每次砍柴會產生多少木材。或者,如果想測試真正的木材產生程式碼,也會用一個會產生特定數列的假亂數產生器,而不是寫死一個種子。
另一個問題是,很多函式參數在呼叫端完全看不出意義。像是 buildHut(1) 或 chopWood(1, ...),根本不清楚那個 1 代表什麼,也不知道測試中出現的多個魔法數字 1 是指同一個值,還是剛好都等於 1 而已。
未解的問題
為什麼要用區塊鏈?
看完直播後,我最大的疑問是:為什麼要用區塊鏈?
Noah 提到他想在區塊鏈上嘗試一些更原創的東西,這點引起了我的興趣,但我還是搞不懂區塊鏈到底能幫上什麼忙。
到目前為止,區塊鏈似乎讓每件事都複雜了十倍,卻沒有提供比「全部塞進單一 SQLite 資料庫」更好的任何好處。
同樣地,我原本想搞懂的「Base 到底是什麼?」這個問題,依然沒有答案。我還是不確定 Noah 為什麼要選擇在 Base 上開發,而不是直接在以太坊或其他鏈上。他有提到 Coinbase 的投入,但我不明白那對像 Noah 這樣的開發者到底意味著什麼。
哇,你的整數可真大啊!
我注意到 Noah 經常在一些看起來完全不需要的地方使用 uint256,例如時間戳記或木材數量。當遊戲目前每次「砍柴」動作只會給予 2 到 4 塊木材時,很難想像需要用 uint256 來儲存這個結果。

這麼大的整數型別難道不會增加 gas 費用嗎?
從我自己實作 EVM 的經驗,我知道以太坊網路會對需要處理的資料收費,所以把一個 256 位元的值推入堆疊,會比推入一個 32 位元的字組貴上 8 倍。(編按:實際上推入 256 位元字組和 32 位元字組的成本是一樣的。感謝 a14u 指正。)
我不確定這只是疏忽,還是實際的 gas 費用比我想像的要低。
為什麼要用底線?
Noah 使用了一種命名慣例,會在所有函式參數名稱前加上底線。

我從沒見過在函式參數名稱前加上底線的慣例,也不太確定 Noah 為什麼這樣做。
我在 Python 和 JavaScript 中看過這種慣例,用來暗示讀者這個變數是私有或受保護的,但函式參數本來不就是私有的嗎?在我有限的 Solidity 程式碼閱讀經驗中,還從沒見過這種寫法。
隨機一篇部落格
留言
登入後參與討論