2026年2月,Base链上流通着70.8枚DGLD黄金代币。攻击者往桥里打了一笔transferFrom,桥说"收到100万枚",然后真给 mint 了100万枚出来。这70.8:1000000的杠杆,比任何DeFi协议都敢玩。
漏洞不在桥,不在L2,在ERC-20标准本身留的那条缝——transferFrom返回true,但币没动。更荒诞的是,这份代码过了两轮独立审计。
一个true引发的连锁反应
我们默认ERC-20是个标准。但"标准"这个词本身就有坑:EIP-20说transferFrom"SHOULD throw on failure",却没说返回true时必须真的转账。这种语义模糊,成了攻击者的提款机。
DGLD的以太坊合约继承了一段 legacy code。攻击者调用transferFrom(attacker, bridge, 100M),合约返回true,攻击者的余额纹丝不动。桥的逻辑很老实:收到true = L1已锁定 = 可以在L2铸造对应数量。Base链于是凭空多了100万枚无黄金背书的DGLD。
桥没做错任何事。它只是相信了一个承诺,而承诺方爽约了。
攻击者把这100万假币砸进Aerodrome DEX,USDC流动性池被抽干。整个过程不需要黑客技术,只需要读懂一份没按套路出牌的合约。
三种"幻影存款"攻击模式
这类漏洞不是孤例。自2022年以来,至少演化出三种利用ERC-20非标准行为的攻击路径。
第一种就是DGLD这种"假成功真躺平"——返回true但余额不变。桥协议、借贷协议、任何相信return value的合约都是潜在受害者。
第二种是手续费代币的会计错位。USDT、STA等代币会在转账时扣费,导致balanceOf(to)的增加量小于amount。如果协议直接给存款人记amount而非实际到账数,账本就会出现"幽灵余额"。
第三种是返回值遗忘。早期USDT在以太坊上根本不返回boolean,用标准IERC20接口调用会直接revert。这也是OpenZeppelin的SafeERC20存在的意义——它用低层call绕过ABI解码,再用extcodesize判断合约是否存在,最后手动解析return data。
但SafeERC20只解决"有没有返回值"的问题,不解决"返回值真不真"的问题。
审计为什么漏掉了
两轮审计都没拦住这个bug,原因很简单:审计师也在按"标准"思维检查。
常规审计清单看的是重入攻击、权限控制、整数溢出。transferFrom返回true?这属于"正常行为",不在怀疑范围内。审计报告里不会写"该函数可能撒谎",因为EIP-20没规定不能说谎。
更深层的问题是行业惯性。我们写代码时默认"ERC-20 = 可互换的、行为一致的",但链上部署着数千个变种。有的代币有回调钩子(ERC-777兼容),有的有黑名单,有的转账会rebase,有的干脆不返回bool。
每个外部代币集成都是一次信任跃迁,而协议开发者经常忘记自己跳了。
原文作者提出的防御方案很朴素:检查前后余额。deposit函数应该先记balanceBefore,执行transferFrom,再查balanceAfter,用实际差额记账。这行代码能挡住DGLD级别的攻击,但会增加gas成本,也会被开发者嫌啰嗦。
另一种思路是维护"已知不良代币"清单,像TokenSniffer那样做风险评分。但清单永远滞后于新部署的恶意合约。
标准战争的代价
ERC-20的成功在于简单,但简单也意味着留白。EIP-20写于2015年,当时没人想到它会承载数百亿美金的桥接资产。标准制定者用了"SHOULD"而非"MUST",给实现者留了自由度,也给攻击者留了空间。
后来的ERC-777试图用更严格的回调机制解决这些模糊地带,结果引入了新的重入攻击面。标准升级的速度永远追不上资金流动的速度。
DGLD事件后,OP Stack团队更新了桥合约文档,提醒开发者"不要假设L1代币行为标准"。但这只是文档更新,不是代码强制。下一个桥协议可能还是会犯同样的错。
攻击者已经证明:在70.8枚真币的池子里,100万枚假币能造成真实的流动性枯竭。这个比例本身,就是对"信任但验证"这句老话的辛辣注释。
你的协议里,有多少地方还在无条件相信那个true?
热门跟贴