Noah Bragg's First Stoke Fire Livestream

Michael Lynch

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

  • Noah 使用 Solidity 進行智慧合約開發。
    • 他聽過 Vyper,但沒用過,因為 Solidity 周邊的生態系比其他任何選擇都成熟得多。
    • 他沒聽過 Huff。

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。像這樣失敗時不印出行號的測試框架,我還真是從沒見過。
  • 為了要斷言 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 程式碼閱讀經驗中,還從沒見過這種寫法。

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

留言