The Analysis of Yearn Finance Attack
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.