手摸手教你如何使用foundry进行测试脚本编写

什么是foundry?foundry是一个solidity智能合约开发工具。可以帮你管理依赖包,编译项目,运行测试脚本,还可以让你通过命令行工具或者script脚本和链上合约进行交互。和hardhat不同的地方是,hardhat我们还是主要用来开发大型的合约项目,但是foundry用来进行编写测试

什么是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

点赞 2
收藏 2
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
合约审计
合约审计
0xb315...216f
全栈开发|合约审计|