介绍Foundry以太坊开发工具箱

  • Paradigm
  • 发布于 2021-12-08 16:56
  • 阅读 2

Foundry是一个用于以太坊应用开发的便携、快速和模块化的工具包。文章详细介绍了Foundry的优势和特性,包括使用Solidity编写测试、模糊测试、覆盖虚拟机状态以及在真实网络状态下运行测试等,并提供了性能比较和应用示例。

概要

我很高兴地宣布一个我们在过去几个月里开发的项目:Foundry

Foundry 是一个便携式、快速且模块化的以太坊应用开发工具包。

为什么选择 Foundry?

如果你想要一个最快、最灵活的以太坊开发环境,且无需配置或第三方库,那么你应该使用 Foundry 的工具,forgecast

致谢: Foundry 是对测试框架 dapptools 的重实现,使用 Rust 编写,速度极快,易于安装,对更广泛的贡献者友好。尽管我们的代码库不是一个分支(并且增加了许多新功能,如支持多个 solc 版本),但这一切都得益于 DappHub 团队多年来的创新工作。谢谢 DappHub!

如果你同意以下以太坊开发建议,那么 Foundry 就适合你。

你应该用 Solidity 编写测试

大多数开发者仍然使用 Javascript 或 Typescript 测试 Solidity,这样并不好。

在 JS 中测试需要大量的样板代码、大量依赖(我指的就是你 node_modules/),以及配置文件。例如,你可以查看 Paul Berg 的 solidity-template

此外,以太坊在 JS 中的数字需要使用 BigNumber 库,例如 bignumber.js、BigNumber、bn 或 JS 的新原生 BigInt,这些都经常导致不兼容问题和生产力损失。

最后,使用 JS 而非 Solidity 测试意味着你需要在实际想要测试的内容之外再操作一个抽象层,这要求你至少熟悉 Mocha 和 Ethers.js 或 Web3.js。这增加了 Solidity 开发者的门槛。

Forge 让你可以用 Solidity 编写测试,这样你可以专注于重要的事情:编写优秀的测试。

一个简单的 Solidity 测试如下所示:

你应该对函数进行模糊测试

即使你对代码中的每个函数进行单元测试并尝试达到 100% 测试覆盖率,仍然可能会有你没有测试到的边缘案例。模糊测试允许 Solidity 测试运行器为你随机选择参数,只需将参数传递给你的 Solidity 测试函数。

这里有一个对上述智能合约进行模糊测试的示例:

模糊测试工具会使用随机值 x 自动尝试这个函数。如果它发现一个输入使得测试失败,它会将其返回给你,以便在修复该错误后创建回归测试:

如果你运行了这个测试,你将在 CLI 中收到以下响应:

它还支持缩小,因此你将得到一个导致你的代码失败的“最小”反例(而不是,例如,一个非常大的数字或字节字符串)。

你应该能够在测试中覆盖 VM 状态

你是否尝试过测试一个需要特定块编号的函数?当然,你可以调用 RPC 方法 evm_mine,但如果你正在测试一个 Compound 治理合约,并且需要前进 40,000 个块怎么办?

你是否尝试模拟一个主网络交易,并希望给你的账户一个特定的代币余额,或者对一个权限函数写入访问?

为了解决这些问题(还有更多),我们提供了 VM 作弊码,这允许在测试运行时修改 VM 的状态。这项服务通过一个在预先配置地址上存在的合约暴露给测试作者。下面的简单示例展示了如何覆盖一个块的时间戳:

关于其他作弊码的更多信息可以在 README 中找到。作弊码非常强大(例如,store 允许你覆盖任意合约存储槽,而 prank 允许你从任意账户发出任意调用)。我们建议花时间使用它们来扩展你的测试所探索的代码路径,并鼓励贡献 新代码

你应该能够在活跃网络状态下运行测试

像大多数以太坊开发工具一样,Forge 通过指定一个节点 URL(并可选地指定一个块编号,如果你有一个归档节点,可以将你的测试锁定到一个块)来支持对远程网络状态的“分叉”。只需运行 forge test --fork-url <你的节点 URL> [--fork-block-number <你想要的块编号>]

你应该能够在运行测试时记录调试信息

Forge 支持使用 ds-testemit log_ 函数进行运行时调试日志记录,也支持 Hardhat 的 console.log

好的,我决定了,我该如何开始?

可以通过运行 cargo install --git https://github.com/gakonst/foundry --locked 安装 Forge 和 Cast(如果你还没有安装 Rust,可以 这里 安装)。我们还计划按平台分发静态构建的二进制文件,并提供 brewapt 包。如果你之前做过自动发布流程,欢迎 联系

安装后,你只需执行 forge init 来创建一个新项目(默认在当前目录)然后 forge build

就这样。你在 <2s 内开始了。

速度如何?

我们已针对一些 Dapptools 库进行了基准测试,以比较测试速度。集成测试也可以在 这里 找到。

项目 Forge Dapp 加速
guni-lev 28.6s 2m36s 5.45x
solmate 6s 46s 7.66x
geb 11s 40s 3.63x
vaults 1.4s 5.5s 3.9x

我们还使用 Forge 和 Hardhat 编译了 openzeppelin-contracts。Hardhat 编译耗时 15.244s,而 Forge 只需 9.449s。另一个基准测试也展示了有前景(且细微的!)结果。也许应该为编译和测试框架建立一个基准测试套件?

愿景是什么?

在 2020 年夏天,我们开始编写 ethers-rs,这是 ethers.js 的 Rust 移植,旨在帮助 MEV 交易者构建更好的机器人。

然后,我们构建了其他基础设施,如 MEV InspectEthers FireblocksEthers FlashbotsArk CircomOptics更多

现在,我们构建了一条灵活的编译管道( ethers-solc 可能支持 新语言Fe),以及 EVM 的抽象( evm-adapters)和 快速测试运行器

我们正逐渐而稳步地为下一个 100 万以太坊开发者和企业家创造模块化、文档齐全且高性能的构建模块。

有关如何使用 Foundry CLI 的更多信息,请查看 README

我们还有许多功能想要添加(不仅要达到 dapptools 的功能平衡,还有更多新的令人兴奋的功能)。你应该查看 GitHub 上的 Foundry

最后,我们正在 Paradigm 内部及其投资组合中招聘 - 请查看我们所有的开放角色,访问 jobs.paradigm.xyz,或通过 georgios@paradigm.xyz 与我联系。

contract Foo {
  uint256 public x = 1;
  function set(uint256 _x) external {
    x = _x;
  }

  function double() external {
    x = 2 * x;
  }
}

contract FooTest {
  Foo foo;

  // 合约的状态在每次测试运行前重置,
  // 每次在部署后调用 `setUp()` 函数。可以把它想象成 JavaScript
  // 中的 `beforeEach` 块
  function setUp() public {
    foo = new Foo();
  }

  // 一个简单的单元测试
  function testDouble() public {
    require(foo.x() == 1);
    foo.double();
    require(foo.x() == 2);
  }

  // 一个失败的单元测试(函数名称以 `testFail` 开头)
  function testFailDouble() public {
    require(foo.x() == 1);
    foo.double();
    require(foo.x() == 4);
  }
}
function testDoubleWithFuzzing(uint256 x) public {
  foo.set(x);
  require(foo.x() == x);
  foo.double();
  require(foo.x() == 2 * x);
}
function testDoubleWithFuzzingCounterExample(uint256 x) public {
  foo.set(x);
  require(foo.x() == x);
  foo.double();
  require(foo.x() == 4 * x);
}
[FAIL. 反例:calldata=0x44735ef10000000000000000000000000000000000000000000000000000000000000001, args=[Uint(1)]] testDoubleWithFuzzingCounterExample (gas: [fuzztest])
address constant CHEATCODE_ADDRESS = 0x7cFA93148B0B13d88c1DcE8880bd4e175fb0DeDF;

interace Vm {
  // 将 block.timestamp 设置为 `x`。
  function warp(uint256 x) external;
}

contract MyTest {
  Vm vm = Vm(CHEATCODE_ADDRESS);

  function testWarp() public {
    vm.warp(100);
    require(block.timestamp == 100);
  }
}
  • 原文链接: paradigm.xyz/2021/12/int...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Paradigm
Paradigm
Paradigm 是一家研究驱动型技术投资公司 https://www.paradigm.xyz/