LESSON 03

Solidity 与 Foundry

测试的价值不在覆盖率数字,而在于能否用独立属性证明正常路径、失败路径、边界值和多步组合行为。

约 90 分钟讲解 + 示例 + Lab + 自测本机记录进度
测试思想

先定义系统保证,再写测试

好的测试描述系统在什么条件下必须成立,而不是把实现代码换一种写法再算一遍。

  • 先写最小合约和明确不变量,再增加功能。
  • 测试正常、失败、边界和组合调用。
  • 固定依赖、编译器、优化和 EVM 配置。
  • 漏洞实验只在本地或明确授权环境执行。

四层测试解决不同问题

Unit

明确输入的行为

权限、零值、边界值、重复调用、暂停状态。

Fuzz

探索大量输入

验证独立属性,不要复制函数内部计算。

Invariant

多步操作后的保证

资产守恒、权限不变、负债不超过资产。

Fork

历史链上集成

固定区块号,在可重复状态中验证真实依赖。

先掌握这组命令

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% 测试覆盖率仍然不能证明合约没有漏洞?

信心
下一课合约漏洞体系