做合约安全久了,会特别清楚一件事:单测写得再多,也只是把你“已经想到的路径”验证了一遍。真正让审计人员焦虑的,往往是那些组合起来的状态路径——一个不是 owner 的地址,在一个没人预料到的时机,调用了一个看似人畜无害的函数,最后把账本搞出了缺口。智能合约 Fuzzing 就是为这种情况准备的。它把大量的随机输入、随机调用者、随机序列丢给合约执行,再用你提前写好的不变量去判断每一次执行是否把状态搞坏了。这篇文章我想把合约领域的 Fuzzing 从原理到工具,再到一条能直接落地的流水线拆开讲清楚,适合正在做合约开发、安全审计,或者对链上安全测试感兴趣的读者。
1. 先想明白一件事:Fuzzing 不是在制造崩溃
很多人第一次接触 Fuzzing,脑子里还留着传统安全里“喂一堆畸形文件让程序崩溃”的印象。放到智能合约上这个直觉不太对。EVM 本身是一个带边界检查的沙箱,普通合约很难像 C 程序那样直接崩出内存破坏。你用 Fuzzing 去跑合约,正常情况下它只会告诉你两类事:某笔交易 revert 了,或者某个你定义的断言失败了。前者常见但大多无关紧要,后者才是真正的金矿。
1.1 输入不仅仅是参数,更是调用序列
传统 Fuzzing 的输入,通常是文件、网络包、命令行参数。智能合约的输入空间要大得多,除了函数参数,还包括调用者是谁、当前区块时间、余额多少、外部合约返回什么数据、前面已经发生了一串什么交易。
举一个例子。一个借贷合约,单次测试时你把“存款”和“取款”分开测,两段都通过,并不代表“先存后取再存”一定安全。更隐蔽的情况是:用户 A 存款后,用户 B 触发清算,然后 A 又在这个被清算后的状态下去取款。这种调用序列才是漏洞滋生的地方,而普通单元测试几乎不可能主动覆盖到。
所以合约 Fuzzing 真正在做的,是把你从“拍脑袋设计用例”里解放出来。你只需要提供一个足够合理的业务动作集合,也就是 Handler,再提供一组不变量,剩下的组合爆炸问题交给工具去搜。工具会自己尝试各种各样的调用顺序、参数组合、账户组合,直到找到一个让你的不变量变假的路径。
1.2 覆盖率和不变量缺一不可
Fuzzing 工具有一个共同的追求:覆盖率。代码覆盖率越高,说明越多的分支被执行过,构造出异常状态的可能性也会更高。但覆盖率只是“过程指标”,不
