Noah Bragg 首次 Stoke Fire 直播
原文由 Michael Lynch 于 发布,订阅该博客
过去一年里,我一直对以太坊很感兴趣,尤其是 Base 生态。问题是,即便花了好几个小时去了解 Base,我依然没搞懂 Base 到底是什么。
每隔几个月,我都会去 Base 官网的开发者板块看看,想找一条适合新手的 Base 开发入门路径,结果看到的好像总是“这里有一些针对特定场景的零散教程,如果有问题,就来 Discord 上问我们吧”。
所以,当我看到自己在 Twitter 上关注的独立创始人 Noah Bragg 开始直播他在 Base 生态上从零搭建一款小游戏的过程时,还是挺期待的。

独立创始人 Noah Bragg 正在直播在以太坊区块链上搭建一款游戏的过程。
我看了Noah 的首场直播,并在下面分享了我的一些收获。
游戏:Stoke Fire
- Stoke Fire 是一款资源管理游戏,玩家需要在村庄里砍柴添火,以维持村庄的温暖。

- 灵感来源
- 《帝国时代 II》,Noah 小时候玩过的一款游戏。
- 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 展示的代码中最脆弱的部分。他必须手动声明好几个不同的数组,然后在添加新 facet 时手动去索引这些数组,有一次还因为要把 facet 数量和某处声明的数组大小对上而折腾了好久。直播间评论区有人说可以用辅助工具自动获取 facets,所以也许有更简单的方法来实现这一点。
Forge
- Noah 使用 Forge 来测试智能合约逻辑。
- Noah 对 Forge 的评价比较积极。
- 他喜欢 Forge 允许他用 Solidity 来写测试,因为这样可以和生产环境的智能合约共享大量代码。
- Forge 让测试不同的区块链状态(比如时间戳和钱包余额)变得很容易。
- Noah 有时很难看懂 Forge 的输出,而我个人则觉得输出非常嘈杂、难以阅读。

我觉得 Forge 的测试输出非常嘈杂
- 当 Forge 中的断言失败时,它不会打印出导致失败的行号。在直播的某个时刻,Forge 打印出的失败信息仅仅是
5 != 4,开发者得自己去源码里找出断言在哪里。我还从没见过哪个测试框架在断言失败时不打印行号的。
- 当 Forge 中的断言失败时,它不会打印出导致失败的行号。在直播的某个时刻,Forge 打印出的失败信息仅仅是
- 为了断言生产代码抛出了某个特定错误,Noah 不得不在测试代码中重新定义这个错误,这让我觉得很奇怪。
Warpcast 也有网页版
- Farcaster 有点像以太坊版的 Twitter 或 Mastodon。
- Farcaster 给人的感觉好像 Warpcast 只有移动端,但看了 Noah 的直播我才发现 Warpcast 也有网页版。
- 不过我觉得创建账号应该还是得用移动端。
直播情况
- 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 提到他想在区块链上尝试一些更具原创性的东西,这也是吸引我的地方,但我还是没弄明白区块链能带来什么帮助。
就目前来看,区块链似乎让所有事情都复杂了 10 倍,却没有带来任何比直接把所有东西放进一个单机 SQLite 数据库更好的好处。
同样,我原本希望弄明白的“Base 到底是什么?”这个问题也依然没有答案。我还是不清楚 Noah 为什么要选择在 Base 上开发,而不是直接在以太坊或其他链上。他提到了 Coinbase 的投入,但我不明白这对像 Noah 这样的开发者意味着什么。
好大的整数啊!
我注意到,Noah 经常在一些看起来完全没必要的地方使用 uint256,比如时间戳或木材数量。当游戏中每次“砍柴”操作只奖励 2 到 4 块木头时,很难想象需要用 uint256 来存储这个结果。

这么大的整数类型不会增加 gas 费用吗?
根据我自己对 EVM 的实现的经验,我知道以太坊网络会对需要处理的数据收费,所以把一个 256 位的值推入栈的成本是推入 32 位字的 8 倍。(编辑:推入一个 256 位字和一个 32 位字的成本其实是一样的。感谢 a14u 的指正。)
为什么要用下划线?
Noah 使用了一种命名规范,即在所有函数参数名前加上了下划线。

我从没见过给函数参数名前加下划线的规范,也不太明白 Noah 为什么要这么做。
我认得这种规范,它在 Python 和 JavaScript 中用来暗示变量是私有/受保护的,但函数参数不本来就是私有的吗?在我有限的 Solidity 代码阅读经历中,我还从没见过这种规范。
随机一篇博客
评论
登录后参与讨论