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] 时有问题