金库份额数字的膨胀攻击:第一笔存款为什么最危险 图 1
金库份额数字的膨胀攻击:第一笔存款为什么最危险 · 图 1

有一类金库漏洞不藏在复杂逻辑里,而藏在最朴素的记账方式中:用份额总数除以资产总数得到每份价值,存款时按这个比例折算你该得多少份。记账看似公平,攻击者却能用两步让第二笔存款人几乎颗粒无收。理解这个被称为份额通胀攻击的结构,是理解金库合约为什么对第一笔存款如此谨慎的钥匙。

先把记账模型摆出来。攻击的靶子是这类写法:金库刚部署时份额总量为零,第一个存一万个币的人按一比一得到一万份;之后每个人存入时,用存入量除以每份单价算出新份额,结果向下取整。漏洞出在第一笔存款的大小上。攻击者把第一笔存款压到协议允许的最小值,比如只存一万个最小计价单位,拿到一万份初始份额——此刻金库里几乎什么都没有,每份价值也几乎等于零。

第二步,攻击者不走存款接口,而是直接向金库地址转入一大笔代币。转账不触发铸造,份额总数不动,资产总数却暴涨,每份价值被人为抬高到荒谬的高位。这时受害者进场:想存入一千万个单位,新份额等于存入量除以每份单价,商不到一,向下取整后归零,或者只剩一点点能被四舍五入吞掉的余量。受害者金库余额照减,份额入账近乎为零,攻击者随后赎回时拿走的是这笔存款的绝大部分。整个过程没有任何越权调用,每一步都严格符合合约规则。

主流防御围绕同一个思想:不让每份单价的初始值被攻击者单方面塑造。最流行的做法是虚拟资产与虚拟份额——记账时在分子分母各加一个固定常数,真实的比例被藏进一层偏移量里,一笔巨额捐赠对每份价值的扰动被稀释到可以忽略,舍入也失去了归零的空间。第二种做法是预留最小份额:部署时立刻铸出最低份额并永久锁进无法取回的地址,保证第一笔真实存款进来时金库已经有了不畸形的基线。ERC-4626 的参考实现把这两条都写进了规范建议,新协议踩中老坑的概率已经明显下降,但大量存量金库和魔改版仍是潜在靶子。

作为普通存款人,你能做的核验分三层。第一层看份额公式:金库页面上显示的每份价值在份额总量极小的新金库里是否稳定,刚开放、总份额只有几千个金库风险最高。第二层查第一笔存款记录:在区块浏览器翻金库最早的交易,第一笔大额铸造是否正常、有没有出现份额数与存款额不成比例的异常小额铸造紧跟大额直转的图案——攻击发生过的金库链上留痕非常醒目。第三层用金额直觉:往一个总份额基数很小的金库存入相对它体量大得多的资金,即便不是攻击场景,你的存款也会单方面改写每份价值、替后来者抬轿或自己吃亏,进出这类金库尽量让金额与金库存量相称。

这类漏洞最值得记住的教训是:DeFi 里公平不一定等于安全。比例记账对所有人都一视同仁,可规则本身给了先手者操纵起点的自由。读合约读的不是有没有恶意函数,而是谁能在规则之内改写别人的价格。份额通胀攻击没有后门、全是明规则,恰恰是这种漏洞最能说明权限审计之外还需要机制审计。

再给一个判断金库新旧与攻防成熟度的快捷办法:查它的部署时间与标准遵循。近两年的新金库大多遵循 ERC-4626 及其对份额计算的防通胀建议,老金库和仿写合约的份额公式五花八门,同样的界面外观背后可能是二十种不同的取整与计价规则。把份额公式当作金库的基因来看,你会对很多看似微小的实现差异产生新的理解:舍入方向朝哪边取整、虚拟偏移加不加、最小份额锁不锁,这三条写在代码里的习惯,基本勾勒出一个开发团队对历史事故的学习程度,也是你在存款前不需要读源码就能从验证过的合约里读出来的少数硬信息。

风险提示:不同金库的份额记账实现差异很大,本文是漏洞机制说明,不构成对任何具体金库安全与否的判断,亦不构成投资建议。

金库份额数字的膨胀攻击:第一笔存款为什么最危险 图 2
金库份额数字的膨胀攻击:第一笔存款为什么最危险 · 图 2