On Aug 4th, 2021, Popsicle Finance suffered a huge financial loss (over $20M) from an attack [1].
After manual analysis, we confirm that it is a double-claiming
attack, i.e., a loophole of its reward system allows the attacker to claim rewards repeatedly.
In the following, we will use an attack transaction to illustrate the attack process and the root cause of the vulnerability.
Background
Popsicle Finance is a yield optimization platform which supports multiple vaults for different chains (e.g., Ethereum and BSC).
Specifically, a user first invokes the deposit
function to provide liquidity, and gets Popsicle LP token (PLP for short). After that, Popsicle Finance will manage the liquidity (interacting with platforms like Uniswap) for the user to make profits.
The user can invoke the withdraw
function to fetch back the liquidity from Popsicle Finance, which will calculate the amount based on the PLP tokens.
The incentive reward comes from the liquidity, which will be accumulated as the time goes by.
The user can invoke the collectFees
function to claim the rewards, which is the key of this attack.
Vulnerability Analysis
In the collectFees
function, token0Reward
and token1Reward
(rewards of the corresponding LP token pair) are calculated for the user. The whole calculation logic is straightforward. However, the function uses a modifier named updateVault
, which is used to update the rewards accordingly.
In short, updateVault
will:
- first invokes the
_earnFees
function to get accumated fee from the pool; - then invokes the
_tokenPerShare
function to updatetoken0PerShareStored
andtoken1PerShareStored
, which represent the amounts of token0 and token1 in the pool for each share; - finally invokes
_fee0Earned
and_fee1Earned
functions to update the rewards for the user (i.e.,token0Rewards
andtoken1Rewards
respectively).
Functions _fee0Earned
and _fee1Earned
share the same logic, i.e., implementing the following fomula (use token0 as an example):
user.token0Rewards += PLP.balanceOf(account) * (fee0PerShare - user.token0PerSharePaid) / 1e18
Note that the calculation is incremental, which means even the user does NOT hold PLP token, the calculated reward remains the value stored in token0Rewards
.
Hence, we can conclude the following two observations:
- user's rewards are stored in
token0Rewards
andtoken1Rewards
, which are not associated with any PLP token; - the
collectFees
function only relies on the status oftoken0Rewards
andtoken1Rewards
, which means that rewards can be withdrawn without holding PLP token.
In the real world scenario, it means a user deposits money to a bank and the bank gives her a certificate of the deposit. Unfortunately, this certificate is neither anti-counterfeiting, nor associated with the user. In such a case, it is possible to make duplicates and spread them to others to gain profits from the bank.
Attack Flow
Briefly, the attacker took the following steps to launch the attack:
- created three contracts. One of them was used to launch the attack, while other two were used to invoke the
collectFees
function to fetch the rewards; - utilized the Flash Loan, i.e., borrowing a large amount of liquidity from AAVE;
- launched the Deposit-Withdraw-CollectFees cycle to perform the attack (there are 8 cycles in total, and lots of liquidity has been withdrawn from multiple valuts of Popsicle Finance);
- returned the Flash Loan back to AAVE, and laundered the profits through Tornado.Cash.
Specifically, the Deposit-Withdraw-CollectFees cycle consists of several steps, which can be easily labelled and clearly summarized by using our online tool [2]:
Profit Analysis
In total, the attacker harvested $20M from Popsicle Finance, including 2.56K WETH, 96.2 WBTC, 160K DAI, 5.39M USDC, 4.98M USDT, 10.5K UNI. After that explotiation, the attacker first exchanged all the other tokens to ETH through Uniswap and WETH, and then performed money laundering by using Tornado.Cash.
Credits
Yufeng Hu, Ziling Lin, Junjie Fei, Lei Wu, Yajin Zhou @BlockSec
(In alphabetical order by the last name)
Medium: https://blocksecteam.medium.com/
Twitter: https://twitter.com/BlockSecTeam
Contact: contact@blocksecteam.com
Reference
[1] https://twitter.com/defiprime/status/1422708265423556611