我审计过一份借贷合约,合约代码只有两百行,实战却藏着一个足以让所有存款归零的案例漏洞。那是合约个再普通不过的周二,合约里有个函数允许用户提取利息,实战但校验余额的案例逻辑写反了——不是检查“用户有多少”,而是合约检查“合约还剩多少”。结果第一个调用者直接拿走了整池资金。实战这让我想起一个事实:智能合约的案例每一行代码,都是合约在跟真金白银打交道。
那次事故之后,实战我开始记录实战中的案例坑。最典型的合约案例是重入攻击。你写一个提款函数,实战先转账后更新余额,案例黑客就能在转账时回调你的合约,再提一次款。2016年The DAO事件就是这么被掏空的,六千多万美金。修复方式倒简单——先更新状态,再转账。可这么基础的原则,现在仍有不少新项目在犯。
另一个常见问题在权限管理。我见过一个NFT合约,管理员地址被硬编码在构造函数里,但部署时传错了参数,把零地址当成了管理员。结果所有铸造权限都被锁死,项目方只能重新部署,之前的用户全部白等。这种错误靠测试很难发现,因为测试环境里没人会故意传零地址。
还有gas优化带来的隐患。有人为了省存储费用,把用户余额用uint64存,觉得几亿绰绰有余。可DeFi的借贷利率是复利计算的,某天清算时发现余额溢出,直接变成负数。我亲眼见过一个机枪池因此崩盘,用户界面显示收益翻倍,实际提现时全是零。
写合约最反直觉的地方在于——越“聪明”的代码越危险。有人喜欢把复杂逻辑塞进一个函数,用各种修饰器控制流程。调试时看着没问题,但某个边界条件触发时,修饰器之间的交互会产生意想不到的权限提升。我倾向于写“笨”合约,一个函数只干一件事,状态变量尽量公开,让人一眼能看出资金流向。
最近帮朋友审计一个众筹合约,他用了代理升级模式,方便后续修bug。但升级函数没加时间锁,管理员可以随时替换合约逻辑。这等于在众筹期间,管理员能直接把募集资金转走。朋友说“我信任自己”,我回他“黑客也信任你”。最后加了48小时延迟,管理员操作需要社区投票才能生效。
说实话,智能合约开发像在走钢丝。没有绝对安全的代码,只有相对谨慎的开发者。我的经验是:每次部署前,把合约当成一个被攻击的目标来读,问自己“如果我是黑客,会从哪个角度下手”。多数漏洞不是藏得深,而是开发者觉得“没人会这么干”。可区块链上,真有人会这么干。