第 11 篇:Cetus——2025 年的溢出仍值两亿美元
2025 年 5 月,Sui 上的 Cetus 被掏空约 2.2 亿美元。原因是一个手写的溢出检查用错了位掩码,导致本该被拒绝的输入通过了检查、随后被静默截断——攻击者用约一个单位的代币换到了天文数量的流动性份额。这一篇讲手写溢出检查为什么比裸算术更危险,以及 Move/Rust 这类「更安全的语言」为什么并不豁免。
2025 年 5 月,Sui 上的 Cetus 被掏空约 2.2 亿美元。原因是一个手写的溢出检查用错了位掩码,导致本该被拒绝的输入通过了检查、随后被静默截断——攻击者用约一个单位的代币换到了天文数量的流动性份额。这一篇讲手写溢出检查为什么比裸算术更危险,以及 Move/Rust 这类「更安全的语言」为什么并不豁免。
2020 年 2 月两起 bZx 攻击,金额只有约一百万美元,却是此后所有价格操纵攻击的模板。它第一次公开演示了三件事:闪电贷让「临时拥有巨额资金」变成一次函数调用、链上现货价格可以在一个区块内被推到任意位置、以及组合多个协议可以造出没人单独设计过的攻击路径。
2020 年 10 月,Harvest Finance 损失约 3400 万美元。它和 bZx 的区别在于攻击的对象:不是借贷协议的抵押品估值,而是一个金库的「份额价格」。攻击者在同一个区块里以低价存入、以高价取出,重复三十多次。这一篇给出一条应该刻在每个金库合约上的不变量:存入再立即取出,必须永远亏钱。
2020 年 11 月,Compound 清算了约 8900 万美元的仓位。没有人攻击它,没有节点撒谎,没有任何代码有 bug——预言机忠实地报告了一个真实发生过的价格,而那个价格来自一次流动性不足的短时脱锚。这一篇讲整个档案里最重要的一类失效:每个组件都符合规范,而系统整体是错的。
2021 年 10 月,Cream Finance 损失约 1.3 亿美元。这次的价格操纵没有动用任何 DEX——攻击者直接往一个金库里「捐」了一笔钱,金库的份额价格立刻翻倍,而 Cream 正是按这个份额价格给抵押品估值的。这一篇讲「捐赠攻击」:一次没有任何函数被调用的状态变更,如何让一个正确的除法算出错误的答案。
2022 年 10 月,Mango Markets 被掏空约 1.14 亿美元。攻击者做的事在协议规则内每一步都合法:他先在自家平台开一个巨大的多头,再去外部薄盘市场把标的价格拉起来,让「未实现盈利」变成可借贷的抵押品。这一篇讲三件事:把浮盈当抵押品的危险、协议自身代币做抵押的循环依赖,以及攻击者用赃款投票决定是否归还的荒诞结局。
2022 年 4 月,攻击者用一笔约十亿美元的闪电贷,在同一笔交易里买到治理代币、投票、通过提案、执行提案、把协议资产转给自己、还清贷款。整个过程完全符合协议规则。这一篇讲治理攻击的标准形态,以及为什么「提案有 24 小时等待期」这句话在这里毫无作用。
2023 年 3 月,Euler Finance 损失约 1.97 亿美元。漏洞是一个「捐赠」函数少了一次健康度检查——它能减少调用者自己的余额,而开发者认为「只减少自己的东西不可能有害」。攻击者用它把自己变成严重资不抵债,再自己清算自己,吃掉最高档的清算折扣。这一篇讲一条应该无条件执行的规则:任何可能让账户变差的操作,结束前必须检查健康度。
这一篇不是单一事故,而是一个从 2021 年至今仍在造成损失的模式:往一个空金库里存 1 wei,再直接捐入一万个代币,下一个存款人无论存多少都会因为向下取整而拿到 0 份额。它把捐赠攻击、舍入方向、除法精度三个问题压缩在同一个公式里,是检验一个人是否真正理解份额数学的最好例子。
2023 年 7 月,几个 Curve 池被重入攻击,损失约 7000 万美元。这些池子的源码里写着重入锁,逐行读挑不出任何毛病——问题是编译它们的那几个 Vyper 版本生成的锁不起作用。这一篇讲编译器是你信任基础的一部分,以及一个已经部署的、不可变的合约踩了编译器 bug 之后能做什么。