foundry 学习记录4-Invariant 测试

Invariant 测试

Invariant(不变量):在任何操作或状态下都必须保持成立的规则。
Invariant 测试:自动验证智能合约在任意操作序列下,不变量是否被破坏。

特点:

测试的是 整个系统或合约的核心逻辑,而不是单个函数。
常与 模糊测试(Fuzzing) 配合使用。
可以捕捉复杂的组合逻辑漏洞。

foundry.toml 配置

[invariant]
runs = 256
depth=128
fail_on_revert = true

是 Foundry (forge) 的模糊测试(fuzz/invariant testing)配置,用在 foundry.toml 里。解释如下:

runs = 256

表示每个 invariant 测试会运行 256 轮随机输入的检验。相当于指定测试的样本数量。默认 256。
为了让测试更快完成,可以设置更低的数比如64,但覆盖度会低一些。

depth = 128

表示每一轮 invariant 测试里,最多会执行 128 步交互/调用(也就是合约函数的随机调用序列长度)。如果设得更高,会测试更多可能的调用顺序,但速度会变慢。

fail_on_revert = true

表示在 invariant 测试过程中,如果遇到合约调用 revert(即失败回滚),就直接判定为 测试失败。
如果设为 false,那么正常的 revert(比如 require 检查不通过)会被当作“允许的行为”,测试不会因为这个中断。

总结:

runs 控制 “有多少次尝试”。
depth 控制 “每次尝试最多调用多少步”。
fail_on_revert 决定 “遇到 revert 算不算失败”。

1.简单合约代码(有问题的代码)

目标 showAlaysBeZero 变量必须为0,无论怎么运行程序

// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
contract MyTest{
    uint256 public showAlaysBeZero = 0;
    uint256 public hiddenValue = 0;
    function doStuff(uint256 data) public {
       if (data == 2){
          showAlaysBeZero = 1;
       }
       if (hiddenValue == 7){
          showAlaysBeZero = 1;
       }
       hiddenValue = data;
    }
}

2.使用不变量测试

1.引入StdInvariant

import {StdInvariant} from "forge-std/StdInvariant.sol";

2.测试合约继承自 StdInvariant
3.targetContract(address(myTest));
设置目标合约myTest,允许随机调用 MyTest 的所有公共和外部函数
以检查不变量是否保持不变
4.不变量测试函数 以invariant_xxx 开头
5.测试

简单测试-合约代码

// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import {Test} from "forge-std/Test.sol";
import {MyTest} from "../../src/MyTest.sol";
import {StdInvariant} from "forge-std/StdInvariant.sol";
contract MyTestTest is StdInvariant,Test{
    MyTest myTest;
    function setUp() public {
       myTest = new MyTest();
       targetContract(address(myTest));
    }
    //模糊测试
    function testFuzz_IsAlwaysBeZero(uint256 x) public {     
        myTest.doStuff(x);
        assertEq(myTest.showAlaysBeZero(),0);
    }
    //不变量测试
    function invariant_IsAlwaysBeZero() public view {     
        assertEq(myTest.showAlaysBeZero(),0);
    }
}

测试

forge test --match-test invariant_IsAlwaysBeZero

输出

Ran 1 test for test/unit/MyTest.t.sol:MyTestTest
[FAIL: invariant_IsAlwaysBeZero replay failure]
        [Sequence] (original: 2, shrunk: 2)
                sender=0x0000000000000000000000000000000000001150 addr=[src/MyTest.sol:MyTest]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=doStuff(uint256) args=[7]
                sender=0x00000000000000000000000000000000000001b5 addr=[src/MyTest.sol:MyTest]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=doStuff(uint256) args=[4435289392501698355663827111978044537409885174853449968331127433325734389 [4.435e72]]
 invariant_IsAlwaysBeZero() (runs: 1, calls: 1, reverts: 1)
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 12.00ms (5.95ms CPU time)

Ran 1 test suite in 156.26ms (12.00ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)

sender=0x0000000000000000000000000000000000001150
addr=[src/MyTest.sol:MyTest]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=doStuff(uint256)
测试出 doStuff 方法,当参数 args=[7] 时有问题

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容