什么是foundry?
foundry是一个solidity智能合约开发工具。可以帮你管理依赖包,编译项目,运行测试脚本,还可以让你通过命令行工具或者script脚本和链上合约进行交互。和hardhat不同的地方是,hardhat我们还是主要用来开发大型的合约项目,但是foundry用来进行编写测试脚本我认为是非常方便的。主要的特点是可以直接使用solidity编写合约测试脚本,无需在JavaScript和solidity之间进行切换。虽然在hardhat也可以编写测试脚本,但是有时候需要切换语言或者对类型进行转换相对会比较麻烦。
安装和配置
Foundryup是foundry的安装器。我们可以直接通过终端命令行进行安装:
curl -L https://foundry.paradigm.xyz | bash
或者要是你喜欢也可以通过源码的方式进行编译安装
# clone the repository
git clone https://github.com/foundry-rs/foundry.git
cd foundry
# install Forge + Cast
cargo install --path ./cli --profile local --bins --force
# install Anvil
cargo install --path ./anvil --profile local --force
# install Chisel
cargo install --path ./chisel --profile local --force
初始化
我们可以通过forge命令行直接初始化新项目
forge init helle_foundry
初始化之后的大概是这么个目录结构:
$ cd hello_foundry
$ tree . -d -L 1
.
├── lib 依赖的包文件目录
├── script 自定义的脚本目录
├── src 合约源码目录
└── test 测试脚本目录
特性和使用
编写测试脚本
测试脚本的基本代码结构:
pragma solidity 0.8.10;
import "forge-std/Test.sol";
contract ContractBTest is Test {
uint256 testNumber;
function setUp() public {
testNumber = 42;
}
function test_NumberIs42() public {
assertEq(testNumber, 42);
}
function testFail_Subtract43() public {
testNumber -= 43;
}
}
在上面的例子中每个test函数都是相互独立的,setUp函数类似于我们的构造函数,会首先进行调用setUp函数进行初始化。在我们实际的操作中,可以直接导入需要测试的合约,然后在setUp中对合约进行初始化。我们只需要一个变量就能调用目标合约中的函数进行测试。
运行测试脚本的命令:
forge test --match-contract "ContractBTest(合约名称)" -vvv
debug
我们可以通过-vvv
和 -vvvv
进行数据跟踪。在打印出来的日志中,我们通过颜色来区分不同的错误类型(如果你的终端支持颜色显示的话):
- 绿色 : 正常的调用
- 红色 : 回滚的调用
- 蓝色 : 作弊码的调用
- 青色 : 日志
- 黄色 : 合约部署
[24661] OwnerUpOnlyTest::testIncrementAsOwner()
├─ [2262] OwnerUpOnly::count()
│ └─ ← 0
├─ [20398] OwnerUpOnly::increment()
│ └─ ← ()
├─ [262] OwnerUpOnly::count()
│ └─ ← 1
└─ ← ()
fuzz测试
contract SafeTest is Test {
function testFuzz_Withdraw(uint256 amount) public {
payable(address(safe)).transfer(amount);
uint256 preBalance = address(this).balance;
safe.withdraw();
uint256 postBalance = address(this).balance;
assertEq(preBalance + amount, postBalance);
}
}
比如上面的提款测试,普通的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")
function testGetTotalShares() public {
assertEq(prizePool.getTotalShares(), 220);
}
时间和区块进行改变
某些合约中可能有时间间隔,比如一个stake asset操作之后,可能需要一个月的时间间隔之后才能进行下一次操作,这个时候我们可以直接去修改evm中的时间
vm.warp(block.timstamp + 30 days);
或者我们也可以对区块进行增加
vm.roll(block.number + 1);
vm.expectRevert
在某些测试中,对于条件不满足的情况下,合约逻辑中可能会存在一些revert的情况,我们需要对这些revert进行捕捉的时候可以用到vm.expertRevert
- expectRevert()
- expectRevert(bytes4 message)
- expectRevert(bytes calldata message)
function testLowLevelCallRevert() public {
vm.expectRevert(bytes("error message"));
(bool revertsAsExpected, ) = address(myContract).call(myCalldata);
assertTrue(revertsAsExpected, "expectRevert: call did not revert");
}
有些合约对一些多次出现的错误可能把它定义为一个error类型,这个时候我们可以通过CustomError.selector
对错误进行捕捉。
vm.expectRevert(CustomError.selector);
vm.expectRevert(
abi.encodeWithSelector(CustomError.selector, 1, 2)
);
对于那些直接revert并没有定义错误的情况下:
vm.expectRevert(bytes(""));
follow我的推进行交流学习:https://twitter.com/nifty12111
TG:@coffiasd
Top comments (0)