諾亞·布拉格的首次 Stoke Fire 直播
過去一年來,我一直對 Ethereum 很感興趣,尤其是 Base 生態系。問題是,即使花了好幾個小時閱讀關於 Base 的資料,我還是不明白 Base 到底是什麼。
每隔幾個月,我就會回頭查看 Base 網站的開發者專區,看看是否有適合新手在 Base 上建構應用的入門路徑,而得到的指引似乎總是「這裡有一些針對非常特定主題、彼此分散的教學,如果有問題,就來 Discord 問我們。」
因此,當我看到自己在 Twitter 上追蹤的獨立創業者 Noah Bragg(諾亞·布拉格) 開始直播他在 Base 生態系上打造一款簡單遊戲的過程時,感到非常期待。

獨立創業者 諾亞·布拉格 正在直播在 Ethereum 區塊鏈上打造遊戲的過程。
我觀看了 諾亞·布拉格的首次直播,並在下方分享我的心得。
遊戲:Stoke Fire
- Stoke Fire 是一款資源管理遊戲,玩家在村莊裡砍柴,為持續燃燒的營火添柴,以保持村莊溫暖。

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

我覺得 Forge 的測試輸出非常雜亂
- 當 Forge 中的斷言(assert)失敗時,它不會印出造成失敗的行號。在直播的某個片段中,Forge 印出的失敗訊息只有
5 != 4,開發者得自行去原始碼中找出斷言的位置。我從未見過測試框架不印出斷言失敗的行號。
- 當 Forge 中的斷言(assert)失敗時,它不會印出造成失敗的行號。在直播的某個片段中,Forge 印出的失敗訊息只有
- 為了斷言諾亞·布拉格的正式程式碼拋出了特定錯誤,他必須在測試程式碼中重新定義該錯誤,這讓我覺得很奇怪。
Warpcast 已推出網頁版應用程式
- Farcaster 就像是 Ethereum 版的 Twitter 或 Mastodon。
- Farcaster 給人的印象是 Warpcast 只有行動版,但我從諾亞·布拉格的直播中才發現 Warpcast 有網頁版應用程式。
- 我認為你仍然需要行動版應用程式來建立帳號。
直播情況
- 諾亞·布拉格的直播最高有 250 位觀眾。
- 到最後才發現,他不確定這是指同時在線的觀眾人數,還是整個直播期間曾經點進來觀看的累計總數。
- 大多數觀眾來自 Twitter。
- 他坦言自己對直播已經有些生疏,直播過程中出現了不少冷場。
可改進之處
便利的開發指令稿
諾亞·布拉格有一次花了好幾分鐘,試圖回想如何只執行單一測試,而不是每次都執行完整個測試套件。最後在聊天室觀眾的協助下,他找到了語法:
forge test --match-contract BuildFacetTest -vvvv對於這種難以記住的命令列語法,我建議撰寫一個便利的指令稿並存放在程式碼庫中。這樣一來,你就不需要在眾多技術堆疊中去記住執行單一測試的正確語法,而是執行類似這樣的指令:
./dev-scripts/run-single-test BuildFacetTest我在自己的幾個程式碼庫中就是這麼做的。
諾亞·布拉格已經在使用 make,所以這些也可以改成 make 指令。
讓測試更易於維護
諾亞·布拉格為建造小屋功能撰寫的最終整合測試如下所示:
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),這讓邏輯變得非常難以理解。為了測試,諾亞·布拉格已將 PRNG 固定為特定種子值,讓隨機序列在每次測試中重複出現,但這仍然讓測試邏輯難以追蹤。
在直播中,諾亞·布拉格不得不盲目地在測試中不斷加入砍柴的次數,來觀察測試何時會通過。他最後乾脆把隨機種子改成另一個值,這樣只需砍兩次柴就能讓測試通過。正因如此,測試無法斷言剩餘木材的精確數量,只能猜測它落在某個區間內。如果不實際執行,就無法驗證這個測試的正確性。
如果是我來處理這段程式碼,我可能會把砍柴功能 mock 掉,改用一個測試用的實作,讓我能明確指定每次砍柴產生的木材數量。或者,如果想測試真實的木材產生程式碼,則使用一個會產生特定數字序列的假隨機數產生器,而不是寫死一個種子。
另一個問題是,許多函式參數在呼叫端難以閱讀。像 buildHut(1) 或 chopWood(1, ...) 這樣的呼叫,完全不清楚 1 代表什麼,也無法判斷測試中多個 1 的魔術數字是指同一個值,還是剛好都等於 1。
尚未解答的問題
為什麼要用區塊鏈?
直播結束後,我最大的疑問是:為什麼要用區塊鏈?
諾亞·布拉格提到他想在區塊鏈上嘗試一些更原創的東西,這也是讓我產生興趣的原因,但我仍然想不通區塊鏈帶來了什麼幫助。
到目前為止,區塊鏈似乎讓所有事情都變得複雜 10 倍,卻沒有提供比起把所有東西都放進單機的 SQLite 資料庫更好的任何好處。
同樣地,對於我原本希望解答的問題「Base 到底是什麼?」我依然沒有答案。我還是不明白為什麼諾亞·布拉格要選擇在 Base 上建構,而不是直接在 Ethereum 或其他鏈上建構。他提到了 Coinbase 的投入,但我不理解這對像諾亞·布拉格這樣的開發者意味著什麼。
哇,好大的整數啊!
我注意到諾亞·布拉格經常在似乎完全不需要的地方使用 uint256,例如時間戳記或木材數量。當遊戲目前每次「砍柴」動作只給予 2 到 4 塊木材時,很難想像需要用 uint256 來儲存結果。

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

我從未見過在函式參數名稱前加上底線的慣例,也不太明白諾亞·布拉格為什麼要這麼做。
我認得這種慣例在 Python 和 JavaScript 中是用來暗示變數是私有或受保護的,但函式參數不就已經是私有的嗎?以我有限的 Solidity 程式碼閱讀經驗,我從未見過這種慣例。
隨機一篇部落格