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 進行翻譯

留言