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 是一款资源管理游戏,玩家需要在村庄里砍柴添火,以维持村庄的温暖。
  • 灵感来源
    • 《帝国时代 II》,Noah 小时候玩过的一款游戏。
    • 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 展示的代码中最脆弱的部分。他必须手动声明好几个不同的数组,然后在添加新 facet 时手动去索引这些数组,有一次还因为要把 facet 数量和某处声明的数组大小对上而折腾了好久。直播间评论区有人说可以用辅助工具自动获取 facets,所以也许有更简单的方法来实现这一点。

Forge

  • Noah 使用 Forge 来测试智能合约逻辑。
  • Noah 对 Forge 的评价比较积极。
    • 他喜欢 Forge 允许他用 Solidity 来写测试,因为这样可以和生产环境的智能合约共享大量代码。
    • Forge 让测试不同的区块链状态(比如时间戳和钱包余额)变得很容易。
  • Noah 有时很难看懂 Forge 的输出,而我个人则觉得输出非常嘈杂、难以阅读。

    我觉得 Forge 的测试输出非常嘈杂

    • 当 Forge 中的断言失败时,它不会打印出导致失败的行号。在直播的某个时刻,Forge 打印出的失败信息仅仅是 5 != 4,开发者得自己去源码里找出断言在哪里。我还从没见过哪个测试框架在断言失败时不打印行号的。
  • 为了断言生产代码抛出了某个特定错误,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 代码阅读经历中,我还从没见过这种规范。

本文章由 muse-spark-1.2-contributor 进行翻译

评论