先定义系统保证,再写测试
好的测试描述系统在什么条件下必须成立,而不是把实现代码换一种写法再算一遍。
- 先写最小合约和明确不变量,再增加功能。
- 测试正常、失败、边界和组合调用。
- 固定依赖、编译器、优化和 EVM 配置。
- 漏洞实验只在本地或明确授权环境执行。
四层测试解决不同问题
明确输入的行为
权限、零值、边界值、重复调用、暂停状态。
探索大量输入
验证独立属性,不要复制函数内部计算。
多步操作后的保证
资产守恒、权限不变、负债不超过资产。
历史链上集成
固定区块号,在可重复状态中验证真实依赖。
先掌握这组命令
forge init vault-lab
cd vault-lab
forge build
forge test
forge test -vvvv
forge coverage
anvil-vvvv 会显示详细 trace。它适合解释调用路径,不应被当作“漏洞扫描器”。
vm.prank改变下一次调用的 msg.sender。
vm.expectRevert断言调用必须以指定方式失败。
deal为地址设置 ETH 或 Token 测试余额。
bound约束 fuzz 输入到有效业务范围。
Lab:证明一个访问控制错误
创建 Vulnerable.sol,让任意地址都能修改 owner。先写攻击测试证明未授权调用成功,再增加权限检查和回归测试。
function test_AttackerCanTakeOwnership() public {
vm.prank(attacker);
vault.setOwner(attacker);
assertEq(vault.owner(), attacker);
}
function test_RevertWhenCallerIsNotOwner() public {
vm.prank(attacker);
vm.expectRevert();
fixedVault.setOwner(attacker);
}完成证据
仓库同时保留 vulnerable、fixed、攻击测试和修复后的回归测试。
课末自测
为什么 100% 测试覆盖率仍然不能证明合约没有漏洞?
信心