Yearn $9M Theft Attack Analysis
Preface
On December 1, 2025, the Yearn protocol encountered a meticulously designed multi-stage complex hacking attack, ultimately resulting in a loss of approximately $9 million in assets. The attack was not a single exploit but rather a series of maneuvers where the attacker leveraged flash loans to manipulate funds and exploited multiple logical flaws in the protocol to gradually control the state of the liquidity pool, culminating in a malicious event that allowed for nearly infinite minting of yETH-related LP tokens and drained the liquidity pool.
This attack used flash loans as initial capital leverage, breaking through the protocol's defenses through multi-step operations. The core attack process can be divided into four key stages: capital preparation, state manipulation, infinite minting, and profit realization. Each step was intricately linked, precisely exploiting the logical vulnerabilities in the protocol's design.
Attack tx: https://etherscan.io/tx/0x53fe7ef190c34d810c50fb66f0fc65a1ceedc10309cf4b4013d64042a0331156
Technical Analysis
First, two flash loans were taken out from Balancer and Aave to borrow wstETH, rETH, WETH, and 0xa35b_ETHx, rETH, wstETH, cbETH.


In the flash loan callback function, the borrowed ETH was used to deposit 1100 ETH into Tornado.Cash: 100 ETH mixed, and then 100 ETH was withdrawn to the malicious contract 0x3e8e7533dcf69c698Cf806C3DB22f7f10B9B0b97 using the Tornado.Cash: 100 ETH withdraw function, triggering its fallback function.


In the fallback function, it can be seen that the last exchange value matches the next remove_liquidity value, indicating that the previous steps were all aimed at converting the assets obtained from the flash loan into a large amount of yETH weighted stableswap pool LP tokens through exchanges and other operations, preparing for the subsequent attack.

At this point, the core attack process officially began.
- First, the LP tokens obtained from the above exchange were completely destroyed through the remove_liquidity function of the yETH weighted stableswap pool, converting them into eight underlying assets distributed according to their shares.
The logic of the remove_liquidity function can be understood as follows: assuming the pool has a total of 10,000 LP Tokens, if you destroy 416.37 LP Tokens, your proportion is: 416.37 / 10,000 = 4.16%.
*Then for each asset, assuming there are 1,000 wstETH in the pool (virtual balance prev_vb). The virtual balance you can withdraw: dvb = 1,000 * 416.37 / 10,000 = 41.637 wstETH, and then divide by the exchange rate to convert to the actual token amount.*
- Next, by repeatedly calling add_liquidity, similar to injecting into a single-sided pool, there were a total of 8 _amounts parameters corresponding to the quantities of different assets to be injected. It was observed that in the previous few loops, index3 [rETH token], index6 [wOETH token], and index7 [mETH token] all input 0, meaning that these three tokens were not added during each liquidity addition.

By injecting single-sided assets in this manner and extracting tokens from the pool, the quantity disparity between rETH, w0ETH, mETH, and other tokens was artificially expanded.
- Immediately after, a massive amount of rETH tokens was injected into the single-sided pool, followed by the key step of remove_liquidity, but with _amount=0.

Why could 0 tokens be extracted? Through the internal implementation of remove_liquidity.

The removeliquidity function did not perform short-circuit processing for 0 amounts, and still executed the complete vbprod calculation loop, updating the global packedpoolvb state based on the artificially created token quantity disparity in the pool.
- Then, the updaterates function was called to only update the pool ratio of index6 [wOETH], and finally, removeliquidity was called to extract tokens from the pool, at which point the quantity of W0ETH in the pool was already very low.

- Similarly, by using a similar method to drain the shares of index6 [w0ETH] and index7 [mETH] from the pool, note that in the first two updates of index6, tokens were immediately extracted using remove_liquidity, while the last index7 [mETH] was only updated and not yet extracted.

At this point, through the above method of massively adding single-sided pools and continuously extracting all pool tokens, the ratio of W0ETH and mETH in the pool was nearly 0.
At this time, a new malicious contract 0xADbE952eBB9b3e247261d2E3b96835f00f721f8E was created, and all tokens were transferred to this contract. Note that in the previous step, the LP tokens obtained from the single-sided addition of rETH were not exchanged for underlying tokens but were also transferred to the new malicious contract.

In the previous attack operation, the tokens that were updated in index7 [mETH] but not extracted were now extracted by calling remove_liquidity, at which point the share of index6 [w0ETH] in the pool was very small, and the share of index7 [mETH] was even smaller.

At this point, the token ratio in the pool was severely imbalanced, and the attacker called add_liquidity again to add liquidity, obtaining a massive amount of LP tokens in the ratio of [1, 1, 1, 1, 1, 1, 1, 9].

By this point, the attacker had obtained a large amount of LP tokens, and the subsequent steps involved profiting through exchange, redeeming, and repaying the flash loan fees.

Attack Review
This attack was a complex multi-stage combination attack, where the attacker exploited three core vulnerabilities in the pool.vy contract: Precision Loss, Yield Stripping, and Zero Supply Initialization.
Stage One: Creating Extreme Imbalance
Operation: The attacker repeatedly called add_liquidity but deliberately avoided index 3 (rETH), index 6 (wOETH), and index 7 (mETH).
Purpose: To artificially create an imbalance in the asset ratios within the pool.
Key Step: Subsequently, a massive amount of rETH was injected.
Consequence: This greatly widened the quantity gap between rETH and other assets (especially wOETH and mETH), creating mathematical conditions for precision loss.
Stage Two: Triggering and Locking Errors
Operation: Call removeliquidity(amount=0).
Principle:
remove_liquidity did not perform short-circuit processing for 0 amounts.
Even without a transfer, the contract still executed the complete vb_prod calculation loop.
Under extreme weight imbalance, the powdown function produced significant downward rounding errors.
The contract wrote this erroneously small vbprod into the global state packedpool_vb.
Essence: This is a "zero-cost" state attack, where the attacker successfully altered the pool's book value without incurring any cost.
Stage Three: Yield Stripping and Share Extraction
Operation:
update_rates([6]) (update wOETH exchange rate).
remove_liquidity (extract assets).
update_rates([7]) (update mETH exchange rate).
Principle:
update_rates triggers _updatesupply. Since vbprod was maliciously suppressed earlier, the system misjudged the pool's value shrinkage, leading to the destruction of LP tokens held by the Staking contract to balance the accounts.
The attacker exploited remove_liquidity to arbitrage before and after the exchange rate updates, gradually draining the shares of wOETH and mETH from the pool.
Result: A large number of shares in the Staking contract were destroyed, passively increasing the LP share ratio held by the attacker, pushing the pool's Total Supply towards 0.
Stage Four: Zero Supply Infinite Minting
Precondition: After the above operations, the pool was emptied, Total Supply was close to 0, and the balances of wOETH and mETH were extremely low.
Operation: add_liquidity, with parameters _amounts=[1, 1, 1, 1, 1, 1, 1, 9].
Principle:
When prev_supply ≈ 0, the _calc_supply iterative formula fails when dealing with extremely small values (1 wei, 9 wei).
The contract erroneously calculated an astronomical amount of LP Token minting.
Result: The attacker received 235,443… yETH LP Tokens out of thin air.
Conclusion
The Yearn attack incident exposed multiple deficiencies in DeFi protocols regarding logical verification in edge scenarios, precision control in numerical calculations, and risk prevention for multi-vulnerability combinations. The attacker's model, utilizing flash loans as a tool, exploiting vulnerability combinations as the core, and obscuring funds as a cover, highlights the current trend of professionalization and complexity in DeFi attacks. The core lessons from this attack include: first, protocols need to strengthen logical verification for edge scenarios such as "zero amounts" and "extreme imbalances" to avoid state alteration risks due to unhandled short-circuit processing; second, attention must be paid to precision loss issues under extreme ratios in numerical calculations, optimizing the calculation logic of key functions like powdown; previously, the Balancer protocol had experienced security incidents due to precision loss, serving as a cautionary tale; third, a multi-dimensional risk monitoring system should be established to provide early warnings for suspicious operations such as high-frequency single-sided liquidity injections and abnormal exchange rate updates. For the entire DeFi industry, this incident once again proves that protocol security requires not only the repair of individual vulnerabilities but also the prevention of attacks utilizing multiple vulnerabilities from a holistic perspective, while enhancing tracking and interception of the attacker's fund flows to improve the overall security defense capability of the industry.







