跳到正文

ERC-4626 首存膨胀攻击:舍入如何令份额归零

了解直接捐赠如何操纵空 ERC-4626 金库的兑换率,使存款人的份额向下舍入至零,以及实现方和用户可以如何降低风险。

更新于

仅供教育用途,不构成投资建议;投资可能导致损失。

直接回答

首存膨胀攻击以空的或接近空的 ERC-4626 金库为目标。攻击者先存入极少量资产以取得首批份额,随后将底层资产直接转入金库。这笔捐赠会在不增加 totalSupply() 的情况下增加 totalAssets(),从而提高每份现有份额的价值。

如果受害者的存款按这个遭到操纵的兑换率换算,整数除法可能将产出的份额向下舍入至极少甚至零。受害者的资产仍留在金库中,而持有全部流通份额的攻击者可以赎回被膨胀的资产索取权。这是兑换率操纵与滑点问题,并非 ERC-4626 接口本身的缺陷。

运作方式

在没有费用或保护性偏移量的简单金库中,存款铸造的份额约为 assets * totalSupply / totalAssets。ERC-4626 要求针对给定资产数量计算份额时,采用有利于金库的向下舍入。正常兑换率下的舍入损失很小,但当份额价格被抬高后,价值低于一份份额的存款可能因舍入而损失 100%。

攻击能否成立取决于具体实现。直接转入 ERC-20 代币可能在不铸造份额的情况下增加金库所计入的资产,尤其是当 totalAssets() 读取金库的代币余额时。攻击者还必须在份额供应量极低时抢先于受害者操作。采用不同会计方式或明确防御措施的金库未必会以相同方式受到攻击。

示例

假设一个存在漏洞的空金库初始兑换率为 1:1。攻击者存入 1 单位资产并获得 1 份份额,然后直接捐赠 999 单位。此时,1 份份额由 1,000 单位资产支持。受害者存入 999 单位,因此朴素计算结果为 999 * 1 / 1,000 = 0 份份额(整数舍入后)。

随后,金库持有 1,999 单位资产,而流通的只有攻击者的 1 份份额。如果存款操作允许产出零份额,并且没有费用或其他约束,攻击者就能用该份额赎回全部 1,999 单位资产:最初存入的 1 单位、捐赠的 999 单位,以及受害者存入的 999 单位。扣除交易成本前,攻击者的毛利润就是受害者的 999 单位资产。

风险与防御

实现方可以通过投入有实际意义的初始流动性、销毁或锁定初始份额、强制要求产出至少一份份额,或采用包含虚拟资产和虚拟份额的兑换率设计来降低风险。OpenZeppelin 的实现会添加虚拟数量,并通过小数位偏移量支持额外的份额精度;在其文档所述模型中,虚拟份额会捕获捐赠的一部分,使操纵无利可图或显著增加其成本。每种防御措施都有其前提,应针对直接转账、舍入边界、费用、亏损和非典型代币行为进行测试。

用户和集成方应将 previewDeposit() 视为报价,而不是有保证的最低值。应通过能够强制执行最低可接受份额数量的函数或路由器提交存款,并在未达到该下限时回退交易。在向新建或份额供应稀少的金库存款前,应检查 totalAssets()totalSupply()、具体实现的换算公式,以及未经请求的代币转账是否会影响会计记录。即使前端报价有利,也无法保护一笔执行顺序可能被改变的交易。

常见误解

  • 误解 1:ERC-4626 本身保证兑换率安全。 该标准定义了通用接口和舍入行为,但不会让所有实现自动免受兑换率操纵。

  • 误解 2:在 deposit() 前立即调用 previewDeposit() 就能保证获得该输出。 两次调用之间或交易执行前,链上状态可能发生变化。存款路径需要一个可强制执行的最低份额下限。

  • 误解 3:只拒绝零份额存款就能消除攻击。 这可以防止最极端的结果,但攻击者仍可能令一笔存款只获得少量份额并遭受巨大的舍入损失。防御措施应限制可接受的滑点,而不应只是要求结果非零。

相关主题

来源

导航

搜索知识库...