
attack transactionattack transactionAnd project code We found that this attack is an attack that exploits the accounting loophole of the project to carry out multiple withdrawals (Double-Claiming Rewards). Below we analyze the attack through the code and attack process.
code analysis
Popsicle Finance is a Yield Optimization Platform involving multiple chains.
The user first calls the deposit function to deposit a certain amount of liquidity into the Smart Pool, and obtains Popsicle LP Token (hereinafter referred to as PLP Token) as proof of share of the deposit. Popsicle Finance will deposit the liquidity provided by users into underlying pools such as Uniswap and earn income.
Users can also call the withdraw function to withdraw liquidity from the smart pool according to the liquidity share represented by the PLP Token held by the user. Popsicle Finance will retrieve the liquidity corresponding to PLP Token from underlying pools such as Uniswap to users.
Finally, the liquidity deposited by the user in the smart pool will generate a certain yield (Yield) over time, which will be accumulated in the user status of the contract. The user can call the collectFees function to get back part of the deposit bonus.
The core function of this attack is the collectFees function. Let's analyze its code step by step. First get the user state stored in userInfo. The token0Rewards and token1Rewards in the user state are rewards accumulated due to user deposits.
Next, calculate the Balance of the Token pair corresponding to the Smart Pool in the contract. If there is enough Balance in the contract, the Reward will be paid to the user according to the amount; otherwise, pool.burnExactLiquidity will be called to retrieve liquidity from the underlying pool and return it to the user.
Finally, the Rewards status recorded in userInfo is updated. Seeing this, the code implementation of the machine gun pool is quite logical. But at the beginning of the function we found the updateVault modifier, this function will run before the function body of collectFees, the loophole may be in the updateVault related function.
The above is the implementation of updateVault related functions. The process is as follows:
First call _earnFees to obtain the accumulated Fee from the underlying pool;
Then call _tokenPerShare to update the token0PerShareStored and token1PerShareStored parameters. These two parameters represent the number of token0 and token1 represented by each share in the pool, that is, the number of Token pairs represented by each share in the smart pool;
Finally, call fee0Earned and fee1Earned to update the deposit Rewards corresponding to this user (ie user.token0Rewards and user.token1Rewards).
The above is the implementation of the fee0Earned and fee1Earned functions. The two functions have the same implementation, and both implement such a formula (take _fee0Earned as an example):
user.token0Rewards += PLP.balanceOf(account) * (fee0PerShare - user.token0PerSharePaid) / 1e18
That is to say, this function will calculate the share of Fee that should be issued to the user based on the original user.token0Rewards and the number of PLP Tokens owned by the user.
But we noticed that this function is incremental, that is to say, even if the user does not hold PLP Token (PLP.balanceOf(account) is 0), this function will still return the deposit reward stored in user.token0Rewards.
So for the entire contract, we found two important logical flaws:
The user's deposit rewards are recorded in user.token0Rewards and user.token1Rewards, and are not tied to any PLP Token or other things in any form.
The collectFees function used to retrieve deposit income only depends on the state of user.token0Rewards and user.token1Rewards of the bookkeeping. Even if the user does not hold PLP Token, the corresponding deposit reward can still be withdrawn.
We imagine an attack process:
The attacker deposits a certain amount of liquidity into the machine gun pool and obtains a part of PLP Token.
The attacker calls collectFees(0, 0), which updates the attacker's deposit rewards, that is, the value of the state variable user.token0Rewards, but does not actually retrieve the deposit rewards.
The attacker transfers the PLP Token to other contracts under his control, and then calls collectFees(0, 0) to update the state variable user.token0Rewards. That is to say, by continuously circulating PLP Token and calling collectFees(0, 0), the attacker copied the deposit rewards corresponding to these PLP Tokens.
Finally, the attacker calls the collectFees function from the above addresses to retrieve the real rewards. Although there is no PLP Token in these accounts at this time, the attacker was able to withdraw multiple rewards because the account was not updated in user.token0Rewards.
Using an example in real life to describe this attack, it is equivalent to depositing money in the bank, and the bank gave me a deposit certificate, but this certificate has no anti-counterfeiting measures and is not bound to me. I copied a few copies of the certificate and sent it to To different people, each of them has withdrawn the interest from the bank by virtue of this certificate.
Attack process analysis
Through the above code analysis, we found a loophole in Popsicle Finance's smart pool implementation. Next, we conduct an in-depth analysis of the attack transaction to see how the attacker exploits this vulnerability.
The overall flow of the attacker is as follows:
The attacker created three transaction contracts. One of them is used to initiate an attack transaction, and the other two are used to receive PLP Token and call the collectFees function of the Popsicle Finance Smart Pool to retrieve deposit rewards.
Lend large amounts of liquidity from AAVE through flash loans. The attacker selected multiple smart pools under the Popsicle Finance project, and lent six types of liquidity corresponding to these smart pools to AAVE.
Do a Deposit-Withdraw-CollectFees cycle. The attacker carried out a total of 8 rounds, respectively attacking multiple smart pools under the Popsicle Finance project, and took out a large amount of liquidity.
Return flash loans to AAVE and launder profits through Tornado Cash.
This attack transaction is mainly composed of several Deposit-Withdraw-CollectFees loops, and the schematic diagram of each loop is shown in the figure above. According to our analysis, the logic is as follows:
The attacker first deposits the liquidity borrowed from the flash loan into the machine gun pool and obtains a certain amount of PLP Token.
The attacker transfers the PLP Token to the attack contract 2.
Attack contract 2 and call the collectFees(0, 0) function of SmartGun Pool to set the corresponding user.token0Rewards and user.token1Rewards states of contract 2.
Attack contract 2 transfers PLP Token to attack contract 3.
Similar to the operation of the attack contract 2, the attack contract 3 calls the collectFees(0, 0) function of the smart pool, and sets the corresponding user.token0Rewards and user.token1Rewards states of the contract 2.
The attack contract 2 transfers the PLP Token back to the attack contract, which calls the withdraw function of the machine gun pool to burn the PLP Token and retrieve the liquidity.
Attack contract 2 and attack contract 3 call the collectFees function to retrieve deposit rewards with false tokenRewards status.
According to our Ethereum transaction tracking visualization system (https://tx.blocksecteam.com/), the transaction call diagram is as follows, and some important transactions are marked in red:
Profit Analysis
The total profit from this attack: 2.56k WETH, 96.2 WBTC, 160k DAI, 5.39m USDC, 4.98m USDT, 10.5k UNI, totaling more than $20,000,000.
After this attack, the attacker first exchanged all other tokens obtained in the attack for ETH through Uniswap and WETH, and then used Tornado.Cash multiple times to wash ETH.
Driven by core security technology, the BlockSec team has long been concerned with DeFi security, digital currency anti-money laundering and digital asset custody based on privacy computing, providing contract security and digital asset security services for DApp project parties. The team has published more than 20 top security academic papers (CCS, USENIX Security, S&P), and its partners have won the title of AMiner's most influential security and privacy scholar in the world (ranked sixth in the world in 2011-2020). The research results have been awarded by CCTV, Xinhua News agency and overseas media reports. Independently discovered dozens of DeFi security vulnerabilities and threats, and won the first place in the world in the 2019 National Institutes of Health Privacy Computing Competition (SGX Track). Driven by technology, the team adheres to the concept of openness and win-win, and works with community partners to build a safe DeFi ecosystem.
Scan the QR code to pay attention to more exciting
https://www.blocksecteam.com/
contact@blocksecteam.com