Noah Bragg's First Stoke Fire Livestream

Michael Lynch

諾亞·布拉格的首次 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(智慧合約) 開發。
    • 他聽過 Vyper。他沒有使用過,因為 Solidity 周邊的生態系比其他任何語言都成熟得多。
    • 他從沒聽過 Huff

來自 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,開發者得自行去原始碼中找出斷言的位置。我從未見過測試框架不印出斷言失敗的行號。
  • 為了斷言諾亞·布拉格的正式程式碼拋出了特定錯誤,他必須在測試程式碼中重新定義該錯誤,這讓我覺得很奇怪。

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 程式碼閱讀經驗,我從未見過這種慣例。

原文由 Michael Lynch 發布

本文章由 muse-spark-1.2-contributor 進行翻譯