北京时间 2021 年 8 月 4 日早上 6 点(区块 12955063),Popsicle Finance 项目下的多个机枪池被攻击,损失金额超过两千万美元,是迄今为止 DeFi 领域发生的损失数额最大的单笔攻击之一。通过分析 攻击交易 及项目代码我们发现,此次攻击是一个利用项目的记账漏洞进行多次提取的攻击(Double-Claiming Rewards)。下面我们通过代码和攻击流程分析此次攻击。 代码分析Popsicle Finance 是一个涉及多个链的机枪池(Yield Optimization Platform)。 用户首先调用 deposit 函数向机枪池存入一定的流动性,并获得 Popsicle LP Token (以下简称 PLP Token)作为存款的份额证明。Popsicle Finance 会将用户提供的流动性存入 Uniswap 等底层池子并获得收益。 用户还可以调用 withdraw 函数,根据用户持有的 PLP Token 所代表的流动性份额,从机枪池取回流动性。Popsicle Finance 会将 PLP Token 对应的流动性从 Uniswap 等底层池子中取回给用户。 最后,用户在机枪池中存的流动性会随着时间产生一定的收益(Yield),会累计在合约的用户状态中。用户可以调用 collectFees 函数取回部分存款奖励。 本次攻击的核心函数正是 collectFees 函数。下面我们逐步分析其代码。首先获得存储在 userInfo 中的用户状态。其中用户状态中的 token0Rewards 和 token1Rewards 是由于用户存款而累积的奖励。 接下来计算该合约中,对应机枪池的 Token 对的 Balance。如果在合约中有足够的 Balance,就按金额将 Reward 支付给用户;否则会调用 pool.burnExactLiquidity 从底层 pool 取回流动性返回给用户。 最后,会将记录在 userInfo 中的 Rewards 状态进行更新。看到这里,机枪池的代码实现还是比较符合逻辑的。但是在函数开头我们发现了 updateVault modifier,这个函数会在 collectFees 的函数体之前运行,漏洞也许在 updateVault 相关的函数中。 以上是 updateVault 相关函数的实现。过程如下:
|