1 Foundry 模糊测试概念
模糊测试(Fuzzing) 是指:
自动生成大量随机输入数据。
调用合约函数。
验证合约在各种随机输入下是否满足断言(assert/require)或者是否抛出异常。
作用:
捕捉边界条件错误。
发现潜在安全漏洞。
提高合约鲁棒性。
Foundry 模糊测试关键点
函数名: 以 testFuzz_ 或者 test_<功能>_Fuzz 开头,Foundry 自动识别。
参数类型:支持 uint, int, address, bytes, bool 等常见类型。
vm.assume(condition):
用于约束随机值范围。
Foundry 会丢弃不满足条件的随机值。
vm.expectRevert():
用于测试函数在非法输入下是否正确 revert。
多次执行:
Foundry 默认运行 256 次不同随机值(可以通过 --fuzz-runs 调整)。
默认运行次数可配置
foundry.toml 文件添加
[fuzz]
runs = 100
简单合约例子
// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
contract MyTest{
uint256 public showAlaysBeZero = 0;
uint256 public hiddenValue = 0;
mapping(address => uint256) public balances;
function doStuff(uint256 data) public {
if (data == 2){
showAlaysBeZero = 1;
}
if (hiddenValue == 7){
showAlaysBeZero = 1;
}
hiddenValue = data;
}
function deposit(uint256 amount) public {
balances[msg.sender] += amount;
}
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
}
}
测试合约
// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import {Test} from "forge-std/Test.sol";
import {MyTest} from "../../src/MyTest.sol";
contract MyTestTest is Test{
MyTest myTest;
function setUp() public {
myTest = new MyTest();
}
function testIsAlwaysBeZero() public {
myTest.doStuff(0);
assertEq(myTest.showAlaysBeZero(),0);
}
//模糊测试
function testFuzz_IsAlwaysBeZero(uint256 x) public {
myTest.doStuff(x);
assertEq(myTest.showAlaysBeZero(),0);
}
// 模糊测试 withdraw
function testFuzz_Withdraw(uint256 depositAmount, uint256 withdrawAmount) public {
vm.assume(depositAmount <= 1e18); // 限制输入范围
vm.assume(withdrawAmount <= 1e18);
myTest.deposit(depositAmount);
if (withdrawAmount <= depositAmount) {
myTest.withdraw(withdrawAmount);
assertEq(myTest.balances(address(this)), depositAmount - withdrawAmount);
} else {
// 应该 revert
vm.expectRevert("Insufficient balance");
myTest.withdraw(withdrawAmount);
}
}
}
1.测试 testFuzz_Withdraw 方法;默认执行 256
forge test --match-test testFuzz_Withdraw
输出
Ran 1 test for test/unit/MyTest.t.sol:MyTestTest
[PASS] testFuzz_Withdraw(uint256,uint256) (runs: 256, μ: 34845, ~: 34350)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 16.09ms (12.86ms CPU time)
测试 执行 300次
forge test --match-test testFuzz_Withdraw --fuzz-runs 300
输出
[PASS] testFuzz_Withdraw(uint256,uint256) (runs: 300, μ: 34965, ~: 34350)
2.测试 testFuzz_IsAlwaysBeZero 方法
逻辑 运行doStuff 方法后 showAlaysBeZero 要不变,依然为0
forge test --match-test testFuzz_IsAlwaysBeZero
输出
Ran 1 test for test/unit/MyTest.t.sol:MyTestTest
[FAIL: assertion failed: 1 != 0; counterexample: calldata=0xf637fa750000000000000000000000000000000000000000000000000000000000000002 args=[2]] testFuzz_IsAlwaysBeZero(uint256) (runs: 0, μ: 0, ~: 0)
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 12.84ms (6.99ms CPU time)
直接定位到 args=[2]] 的时候有问题,可以修改源码