foundry的介绍
什么是foundry?
foundry是一个solidity智能合约开发工具。可以帮你管理依赖包,编译项目,运行测试脚本,还可以让你通过命令行工具或者script脚本和链上合约进行交互。和hardhat不同的地方是,hardhat我们还是主要用来开发大型的合约项目,但是foundry用来进行编写测试脚本我认为是非常方便的。主要的特点是可以直接使用solidity编写合约测试脚本,无需在JavaScript和solidity之间进行切换。虽然在hardhat也可以编写测试脚本,但是有时候需要切换语言或者对类型进行转换相对会比较麻烦。
安装和配置
Foundryup是foundry的安装器。我们可以直接通过终端命令行进行安装:
1 | curl -L https://foundry.paradigm.xyz | bash |
或者要是你喜欢也可以通过源码的方式进行编译安装
1 | clone the repository |
初始化
我们可以通过forge命令行直接初始化新项目
1 | forge init helle_foundry |
初始化之后的大概是这么个目录结构:
1 | cd hello_foundry |
特性和使用
编写测试脚本
测试脚本的基本代码结构:
1 | pragma solidity 0.8.10; |
在上面的例子中每个test函数都是相互独立的,setUp函数类似于我们的构造函数,会首先进行调用setUp函数进行初始化。在我们实际的操作中,可以直接导入需要测试的合约,然后在setUp中对合约进行初始化。我们只需要一个变量就能调用目标合约中的函数进行测试。
运行测试脚本的命令:
1 | forge test --match-contract "ContractBTest(合约名称)" -vvv |
debug
我们可以通过-vvv
和 -vvvv
进行数据跟踪。在打印出来的日志中,我们通过颜色来区分不同的错误类型(如果你的终端支持颜色显示的话):
- 绿色 : 正常的调用
- 红色 : 回滚的调用
- 蓝色 : 作弊码的调用
- 青色 : 日志
- 黄色 : 合约部署
1 | [24661] OwnerUpOnlyTest::testIncrementAsOwner() |
fuzz测试
1 | contract SafeTest is Test { |
比如上面的提款测试,普通的test测试函数需要自定义一个固定的amount金额,但是这个指定的amount不能覆盖到所有的情景,另一个选择就是使用testFuzz.fuzz测试的函数名和普通测试对比只是多了fuzz,需要注意的是所有的测试函数需要用test
或者testFuzz
开头否则是不会被认为是一个测试函数的。
console.log日志打印
在solidity中打印日志需要提前对日志内容的类型进行定义。foundry支持下面的一些数据类型进行日志打印,可以帮助我们进行debug:
- console.logInt(int i)
- console.logUint(uint i)
- console.logString(string memory s)
- console.logBool(bool b)
- console.logAddress(address a)
- console.logBytes(bytes memory b)
- console.logBytes1(bytes1 b)
- console.logBytes2(bytes2 b)
- console.logBytes32(bytes32 b)
对于一些更加复杂的数据结构,比如我们的目标合约中返回了一个struct类型的数据。我们可以直接使用targetContract.StructName对数据进行定义。
assert
foundry中主要有3种类型的assert
- assert(conditon) 判断一个bool值
- assertEq(a,b) 判断a,b两个值是否相等
- assertTrue(condition,”reason”)
1
2
3function testGetTotalShares() public {
assertEq(prizePool.getTotalShares(), 220);
}
时间和区块进行改变
某些合约中可能有时间间隔,比如一个stake asset操作之后,可能需要一个月的时间间隔之后才能进行下一次操作,这个时候我们可以直接去修改evm中的时间
1 | vm.warp(block.timstamp + 30 days); |
或者我们也可以对区块进行增加
1 | vm.roll(block.number + 1); |
vm.expectRevert
在某些测试中,对于条件不满足的情况下,合约逻辑中可能会存在一些revert的情况,我们需要对这些revert进行捕捉的时候可以用到vm.expertRevert
- expectRevert()
- expectRevert(bytes4 message)
- expectRevert(bytes calldata message)
1 | function testLowLevelCallRevert() public { |
有些合约对一些多次出现的错误可能把它定义为一个error类型,这个时候我们可以通过CustomError.selector
对错误进行捕捉。
1 | vm.expectRevert(CustomError.selector); |
1 | vm.expectRevert( |
对于那些直接revert并没有定义错误的情况下:
1 | vm.expectRevert(bytes("")); |
Follow我的推进行交流学习:https://twitter.com/coffiasse
TG:@coffiasd