我们进一步分析代码,看下在代码中发生了什么:首先从链上信息我们能看到 rebase 操作调用的是 YAMRebaser 合约的 YAMRebaser::rebase() 函数(我们先跳过这个函数稍后再讲),我们最终发现它通过调用 YAM合约(0xa923af6d05993495257a872ec69dbbf01501eb0e)的 rebase() 函数重新计算totalSupply(代码逻辑如下图2中所示),在第340行的 totalSupply 赋值操作可以看到,这一行代码有个明显的错误——没有除 BASE,从而导致 totalSupply 的值暴增了10^18倍。 YAM 官方在第一次 rebase 以后发现了这个问题,于是披露 rebase bug 事件启动了投票拯救行动。 图2. YAMToken::rebase() 得到一个异常大的totalSupply值 而在12小时之后,YAM 又触发了第二次 rebase(https://oko.palkeo.com/0x32735e9e9aac51739b5725a225be6c7a3851f422be986d0f4f4bc0ec475ee286/),这个数据又是以基于错误的 totalSupply 来计算的,从而导致 initSupply 的数值同样出现了异常。 图3. 第二次 rebase 资产变化 我们继续分析造成 initSupply 异常的成因,关键在上面提到过 YAMRebaser::rebase() 函数,这个函数实现的主要逻辑:先基于 yam.totalSupply() 计算出本次 rebase 需要增发的 YAM 数额 mintAmount,在 afterRebase() 函数经过数层调用后进入 YAM 的 _mint() 函数,基于异常的 mintAmount 给 initSupply 进行赋值。由于在第一次 rebase 中,totalySupply 已经变成一个极大值,所以基于此异常值的后续一列操作(如图4中红色箭头所示)最终导致 initSupply 也计算错误,变成了一个天文级的数值。 图4. YAMRebaser::rebase() 用错误的totalSupply计算initSupply 当第一次 rebase 出现异常时,项目方已经发现问题并决定提出一个修复系统的提案(proposal),希望通过投票的方式将此提案排入执行队列并且执行。当此题案收到足够多的投票,治理合约(Governor)允许任何人通过调用 GovernorAlpha::queue() 函数将此题案排入执行队列。但由于此治理合约代码逻辑的实现,导致无论是在第二次 rebase之 前或是之后进行修复,都无法正确执行这个拯救行动。 为什么说项目方准备工作完成的太晚了? 我们看下图中的 GovernorAlpha::queue() 代码,我们注意到了在调用 _queueOrRevert 函数之前的第224行中设置变量 eta = current timestamp + timelock.delay(12.5 小时),这就使得生效时间必然在加入队列的12.5小时以后,而第二个 rebase 时间是与第一次间隔12小时,这就意味着要执行成功需要将拯救行动提前到第一次 rebase 之前至少半小时以上,否则将永远无法执行。 (责任编辑:admin) |