MasterChef 智能合约可能是在 DeFi 最早一波狂潮时,被重新部署最多次的一个合约,很多的 DeFi项目都会修改该合约来实现自己的流动性挖矿功能。本文 Amber Group 将分享并解析 10 月团队发现的失误项目 Dinosaur Egg其流动性池漏洞案例。
(本文由 Amber Group 研究团队撰着、提供,不代表动区立场。)
2020 年夏天, SushiSwap 创办人 Chef Nomi(@NomiChef)1 所发布的 MasterChef 智能合约 2 可能是最早的一波 DeFi 狂潮中被重新部署最多次的一个合约。很多 DeFi 项目会略为修改 MasterChef 合约来实现他们流动性挖矿的功能。
然而,过去一年多的时间内,有几个项目在修改 Nomi 主厨的食谱过程中犯了错误,造成无法挽救的损失 [3][4]5。2021 年 10 月 6日,我们也发现了一个失误的项目,所幸在还没造成损失之前便成功修复了漏洞并且完成升级。
这次事件的主角 Dinosaur Eggs 项目的 LiquidityPool 智能合约 6 也是一个『加强版』的 MasterChef合约,其主要修改的部分是新增了「addtionalRate」功能。这个功能是为了让特定的 NFT 持有者在存入 LP tokens时可以获得额外的奖励,最高可达 10%,条件是存入 LP tokens 之前必须『烧掉』特定的 NFT 资产。
从上面的程式码片段 327-328 行可以看到(_amount*user.addtionalRate)会被加到 user.addtionalAmount里头,而这个 addtionalAmount 会在计算奖励时被加入计算。
漏洞出现在从 MasterChef 继承而来的紧急逃生出口 — emergencyWithdraw() 函数。这个函数是用来让 user可以在紧急情况把所有存入的 LP tokens 一次取出来,不考虑奖励计算。
然而,前面提到的 user.addtionalAmount 在这个函数里没有被 reset,也就是说下一次的 harvest() call 会出现 user没有存入任何 LP tokens 的状态下仍然可以领取奖励的情况。
从 harvest() 函数的第 342 行可以看到 pendingAmount 的计算是 (user.amount+user.additionalAmount)*pool.accRewardPerShare – user.rewardDebt。
由于之前的 emergencyWithdraw() 已经 reset 了 user.amount 以及 user.rewardDebt,pendingAmount 就变成了 (user.addtionalAmount*pool.accRewardPerShare)。
因此,攻击者可以在没有任何 LP tokens 存入的状态下,不停的来回利用 harvest() 跟 emergencyWithdraw() 把所有的 reward tokens 取出。
上面的攻击合约程式码验证了上述的想法,在 prepare() 函数里,我们刻意铸造了一个 NFT (第 36 行)并且透过第 40 行的 additionalNft() 函数启动了前面提到的 addtionalRate 机制,随后我们 deposit() 了一部分 LP tokens 到 LiquidityPool。
为了获得额外的奖励,我们在 trigger() 函数里利用回圈多次调用 emergencyWithdraw() 与 harvest() 函数(第 48 – 51 行)。
从上面的 eth-brownie 截图可以看到,我们只用了 30 个 LP tokens(DsgLP)就能获取上千个 rewardtokens(DSG)。在同样的情况如果只是单纯的 harvest() 没有刻意 emergencyWithdraw(),只有不到一个的 DSG 奖励 。假设攻击者利用闪电贷款生成大量 LP tokens,还能更进一步扩大获利。
在我们向 DSG 团队通报这个漏洞之后,他们很快的确认问题并且展开了修补工作。
新版的 LiquidityPool 合约部署上线后,DSG 团队通知了用户将资产从旧版取出后迁移到新版,同时也将旧版的流动性挖矿暂停。
幸运的是,在完成迁移之前,并没有真正的攻击发生,DSG 团队也根据漏洞赏金计画给了我们 $10,000 等值的 DSG tokens 奖励 7 。这部分奖金 8 随后将被用来捐助开放文化基金会,支持开源软体发展。
– 图源:由 Amber Group –
LINE 与 Messenger 不定期为大家服务
Leave a Reply