极快的测试,不再使用 BigNumber.js,只有 Solidity 代码
- 原文链接:https://soliditydeveloper.com/foundry
- 译文出自:登链翻译计划
- 译者:翻译小组
- 校对:Tiny 熊
- 本文永久链接:learnblockchain.cn/article…
也许你是编程新手,并且刚刚开始学习 Solidity?对你来说,一个恼人的问题可能是,你基本上需要学习第二种语言(JavaScript/TypeScript)来编写测试。这无疑是一个缺点,现在随着新的foundry框架的出现,这个缺点已经消失了。
更新: 登链社区组织翻译了Foundry 文档: https://learnblockchain.cn/docs/foundry/i18n/zh/
但是,即使你精通JavaScript,一般来说,把所有的东西都放在同一个技术栈里会更好。使用foundry可以极大地帮助你用更少的代码行编写测试,而且再也不会被BigNumber.js / bn.js所困扰。
foundry是用Rust编写的,速度极快。尽管它很新,但在生产中也是非常可用的。如果我今天要开始一个新的项目,我一定会用Foundry来尝试。
感谢devtooligan提供的图片。
所以我们来实现一个ERC-20并写一些测试。创建 ERC20 合约,你也可以参考社区的这篇文章如何创建并部署ERC20代币 。
安装foundry的具体步骤将取决于你的系统。我在 Mac OS 上使用 zsh 作为终端的所需命令在下边。对于其他系统,请查看指南这里。这将给我们带来两个新的二进制文件:forge
和cast
。
$ curl -L https://foundry.paradigm.xyz | bash
$ source ~/.zshrc
$ brew install libusb
$ foundryup
要创建一个新的项目,我们现在可以使用forge init
。你可以创建一个空项目,或者从一个模板开始。
我发现一个很好的模板:
$ forge init --template https://github.com/FrankieIsLost/forge-template
这将包括一些我们要使用的测试工具。
或者使用我创建的模板,其中包含本帖中所有的示例代码,见最后的说明。
现在让我们创建一个ERC-20合约,并对其进行一些测试。首先让我们安装Openzeppelin合约并更新std库。使用forge,这可以通过以下方式完成:
$ forge install OpenZeppelin/openzeppelin-contracts@v4.5.0
$ forge update foundry-rs/forge-std
现在将该库添加到现有的remappings文件中:
forge-std/=lib/forge-std/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/
现在使用Openzeppelin合约来创建一个新的合约,只要把现有的文件重命名为MyERC20.sol
:
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol";
contract MyERC20 is ERC20 {
constructor() ERC20("Name", "SYM") {
this;
}
}
现在在文件MyErc20.t.sol
中,我们可以创建一个基础设置(Setup)。在foundry中,有一个setUp
函数,可以定义它来使合约进入不同的状态并创建一些地址。除了ERC20合约本身,我们还将从forge-std、ds-test和utils导入一些东西。
把相应的测试文件重命名为MyErc20.t.sol
:
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {console} from "forge-std/console.sol";
import {stdStorage, StdStorage, Test} from "forge-std/Test.sol";
import {Utils} from "./utils/Utils.sol";
import {MyERC20} from "../MyERC20.sol";
contract BaseSetup is MyERC20, DSTest {
Utils internal utils;
address payable[] internal users;
address internal alice;
address internal bob;
function setUp() public virtual {
utils = new Utils();
users = utils.createUsers(5);
alice = users[0];
vm.label(alice, "Alice");
bob = users[1];
vm.label(bob, "Bob");
}
}
对于基本的setUp
函数,我们只需使用模板中已有的utils函数。它们允许我们创建一些持有以太币的用户地址。让我们称第一个地址为Alice,第二个为Bob。
我们可以使用Vm contract来修改EVM的一些低级别的东西,例如给一个地址贴上标签,这样在堆栈跟踪中我们就可以很容易地用标签来识别它。
现在,让我们创建一些设置来转移代币......
现在我们可以创建一个转账代币的设置(Setup)。类似于JavaScript mocha测试中的 beforeEach
和 describe
的设置,当现在所有的设置都使用 Solidity ,我们可以编写一个公共的 setUp
函数和合约。在setUp
函数中不要忘记调用BaseSetup的 setUp
。
而且使用 console.log! 可以在堆栈追踪中打印日志,可以用console.log记录你当前所处的场景类型。
contract WhenTransferringTokens is BaseSetup {
uint256 internal maxTransferAmount = 12e18;
function setUp() public virtual override {
BaseSetup.setUp();
console.log("When transferring tokens");
}
function transferToken(
address from,
address to,
uint256 transferAmount
) public returns (bool) {
vm.prank(from);
return this.transfer(to, transferAmount);
}
}
而我们现在也有了一个简单的转账函数,可以在测试中使用。注意,为了让vm.prank
工作,你必须进行实际调用,使用this.transfer
而不是只使用transfer
。
我们创建两个场景:
在setUp
中,不要忘记调用之前的setUp
, 你也可以使用super()
,但我更喜欢明确的方式。然后我们可以使用ds-test库中的断言帮助函数。它将给你几个断言助手:用于断言相等(assertEq
),小于(assertLe
)和大于(assertGe
),包括我们将使用的带decimals
的标记的选项。
场景1:
contract WhenAliceHasSufficientFunds is WhenTransferringTokens {
uint256 internal mintAmount = maxTransferAmount;
function setUp() public override {
WhenTransferringTokens.setUp();
console.log("When Alice has sufficient funds");
_mint(alice, mintAmount);
}
function itTransfersAmountCorrectly(
address from,
address to,
uint256 amount
) public {
uint256 fromBalance = balanceOf(from);
bool success = transferToken(from, to, amount);
assertTrue(success);
assertEqDecimal(
balanceOf(from),
fromBalance - amount, decimals()
);
assertEqDecimal(
balanceOf(to),
transferAmount, decimals()
);
}
function testTransferAllTokens() public {
uint256 t = maxTransferAmount;
itTransfersAmountCorrectly(alice, bob, t);
}
function testTransferHalfTokens() public {
uint256 t = maxTransferAmount / 2;
itTransfersAmountCorrectly(alice, bob, amount);
}
function testTransferOneToken() public {
itTransfersAmountCorrectly(alice, bob, 1);
}
}
场景2:
contract WhenAliceHasInsufficientFunds is WhenTransferringTokens {
uint256 internal mintAmount = maxTransferAmount - 1e18;
function setUp() public override {
WhenTransferringTokens.setUp();
console.log("When Alice has insufficient funds");
_mint(alice, mintAmount);
}
function itRevertsTransfer(
address from,
address to,
uint256 amount,
string memory expRevertMessage
) public {
vm.expectRevert(abi.encodePacked(expRevertMessage));
transferToken(from, to, amount);
}
function testCannotTransferMoreThanAvailable() public {
itRevertsTransfer({
from: alice,
to: bob,
amount: maxTransferAmount,
expRevertMessage: "[...] exceeds balance"
});
}
function testCannotTransferToZero() public {
itRevertsTransfer({
from: alice,
to: address(0),
amount: mintAmount,
expRevertMessage: "[...] zero address"
});
}
}
vm也允许你模拟一个调用。例如,你可以模拟一个向bob转账和金额的调用,并返回false。你还可以使用clearMockedCalls()
来清除模拟。
function testTransferWithMockedCall() public {
vm.prank(alice);
vm.mockCall(
address(this),
abi.encodeWithSelector(
this.transfer.selector,
bob,
maxTransferAmount
),
abi.encode(false)
);
bool success = this.transfer(bob, maxTransferAmount);
assertTrue(!success);
vm.clearMockedCalls();
}
你也可以使用stdStorage
功能,直接从状态中检索数据。例如,要直接从状态中读取余额,首先计算存储槽,如下代码所示。然后使用vm.load
将其加载出来:
using stdStorage for StdStorage;
function testFindMapping() public {
uint256 slot = stdstore
.target(address(this))
.sig(this.balanceOf.selector)
.with_key(alice)
.find();
bytes32 data = vm.load(address(this), bytes32(slot));
assertEqDecimal(uint256(data), mintAmount, decimals());
}
你也可以在forge中使用模糊测试。只要做一个带有输入变量的测试函数,forge会自动为你进行模糊测试。如果你需要有一定的界限,你可以通过确切的输入类型来限制范围,或者使用vm.assumption
来排除摸个值,和(或者)用modulo来限制输入到一个确切的范围:
function testTransferFuzzing(uint64 amount) public {
vm.assume(amount != 0);
itTransfersAmountCorrectly(
alice,
bob,
amount % maxTransferAmount
);
}
$ forge test -vvvvv
你可以在不同的verbose级别下运行forge测试。增加v的数量,最多5个。
)
到目前为止,更多项目已经取得了很好的经验切换他们的测试。
如果你也想试一试,想从上面的代码开始,请使用以下模板:
$ mkdir my-new-erc20 && cd my-new-erc20
$ forge init --template https://github.com/soliditylabs/forge-erc20-template
Solidity 编码快乐!
本翻译由 Decert.me 赞助支持。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!