The Analysis of Yearn Finance Attack

Eocene | Security
6 min readApr 14, 2023

--

Overview

On April 13, 2023, Yearn Finance suffered an attack, resulting in a loss of approximately 10 million U.S. dollars.. This article will analyze the attack process and the reasons for the vulnerability.

Attack Analysis

This is an attack transaction:

https://etherscan.io/tx/0xd55e43c1602b28d4fd4667ee445d570c8f298f5401cf04e62ec329759ecda95d

The attacker initiated a flash loan from Balancer, borrowing 5 million DAI, 5 million USDC, and 2 million USDT:

Then, on Curve, the attacker exchanged 5 million DAI for 695,000 USDT and exchanged 3.5 million USDC for 151 USDT:

The attacker called the recommend function of IEarnAPRWithPool to check the current APR. At this moment, only the APR in Aave was not equal to 0:

The attacker then transferred 800,000 USDT to the attack contract at 0x9fcc1409b56cf235d9cdbbb86b6ad5089fa0eb0f. Within this contract, the attacker called the repay function of Aave: Lending Pool V1 multiple times, helping others repay their debts, with the aim of making Aave's APR equal to 0:

The attacker called the deposit function of yUSDT, staking 900,000 USDT and receiving 820,000 yUSDT in return:

Next, the attacker called the mint function of bZx iUSDC, using 156,000 USDC to mint 152,000 bZx iUSDC and transferred them to Yearn yUSDT:

The attacker called the withdraw function of Yearn: yUSDT, exchanging 820,000 yUSDT for 1,030,000 USDT. At this point, only the bZx iUSDC transferred by the attacker remained in the contract:

Next, the attacker called the rebalance function of Yearn: yUSDT, burning the bZx iUSDC:

Then, the attacker transferred 1/e6 USDT to the yUSDT contract and called the deposit function, staking 10,000 USDT and obtaining 1,252,660,242,850,000 yUSDT:

Then, in Curve, the attacker exchanged 70,000 yUSDT for 5,990,000 yDAI, 400 million yUSDT for 4,490,000 yUSDC, and 1,240,133,244,352,200 yUSDT for 1,360,000 yTUSD:

Then, in the Yearn: yDAI and Yearn: yUSDC contracts, the attacker called the withdraw function separately to extract 6.78 million DAI and 5.62 million USDC, and returned the borrowed tokens to repay the flash loan:

Vulnerability Analysis

The most critical point in this attack was that the attacker minted 1,252,660,242,850,000 yUSDT using 100,000 USDT. See the implementation of the deposit function:

As we can see, the number of shares is related to the pool variable, the smaller the pool, the larger the shares. The value of the pool is obtained from the _calcPoolValueInToken function:

After the attacker’s called rebalance, only USDC remained in the contract, but the _balance() function retrieves the balance of USDT. Therefore, the pool value at this moment was 1 (attacker transferred):

This is clearly a configuration error by the project team. In the yUSDT contract, there should only be USDT-like tokens, but the fulcrum variable is related to the USDC-based bZx IUSDC token. As a result, the USDC in yUSDT is not included in the balance calculation:

And Why was the attacker able to call the rebalance function to burn the bZx iUSDC tokens? See the implementation of the rebalance function:

We can see that there are “redeem” and “burn” in _withdrawAll(). Therefore, we need to make newProvider != provider hold true. The implementation of the recommend() function is as follows:

The attacker manipulated the newProvider by controlling the return value of IIEarnManager(apr).recommend(token) so that both are 0:

How to make both return values 0? The return value of this function is related to the calculated APR in various DeFi platforms. Since there are no pools in Compound, bZx, and dYdX, it is only necessary to control Aave (Aave: Lending Pool Core V1):

To make its value return 0, the first return value of the apr.calculateInterestRates function needs to be 0:

That is, make the currentLiquidityRate 0. This value is related to _totalBorrowsStable and _totalBorrowsVariable. When both of these values are 0, the currentLiquidityRate is 0:

_totalBorrowsVariable is 0, which means no one is borrowing from Aave: Lending Pool Core V1 at this time. In order to achieve this condition, the attacker repaid all the debts in the pool:

Finally, the attacker made _totalBorrowsVariable become 0, so it was able to call the rebalance function to burn bZx iUSDC:

Summary

The root cause of the Yearn Finance attack was the contract configuration error. The attacker exploited the vulnerability through various ingenious techniques, and finally made a profit of about 10 million U.S. dollars.

About US

At Eocene Research, we provide the insights of intentions and security behind everything you know or don’t know of blockchain, and empower every individual and organization to answer complex questions we hadn’t even dreamed of back then.

Learn more: Website | Medium | Twitter

--

--

Eocene | Security

Smart contract audit, attack analysis, web3 security research, on-chain monitor and alert. Powered by Eceone Research.