Author: Jiu Jiu & Lisa
Editor: 77
Background
On December 1, 2025, the well-established decentralized yield aggregation protocol Yearn was attacked, resulting in a loss of approximately $9 million. Below is a detailed analysis by the Slow Fog Security Team regarding this attack incident:

Root Cause
In Yearn's yETH Weighted Stableswap Pool contract, the logic of the function that calculates the supply (_calc_supply) used an unsafe mathematical operation approach, which allowed for overflow and rounding errors during calculations. This resulted in significant deviations when calculating the new supply and the product of the virtual balance, ultimately enabling the attacker to manipulate liquidity to a specific value and mint an unexpected amount of LP tokens for profit.
Prerequisite Knowledge
yETH is an automated market maker pool (AMM) composed of various Ethereum liquid-staking derivatives (LST), where users can deposit LST into the pool to provide liquidity and receive yETH tokens. Each LST asset has a corresponding rate provider, and the asset balance in the liquidity pool multiplied by the exchange rate is known as the 'virtual balance' (vb), calculated as follows:

The contract tracks a variable D, which represents the total LP supply when the liquidity pool is in a fully balanced state. Any increase or decrease in the D value will correspondingly mint or burn an equal amount of LP tokens (yETH). This mechanism ensures a 1:1 peg, calculated as follows:

Where σ is the total virtual balance of all assets (vb_sum), and π is the total product of virtual balances (vb_prod). The total product of virtual balances (vb_prod) will be updated synchronously when calculating the new supply amount, calculated as follows:

Attack Analysis
Attack Transaction: 0x53fe7ef190c34d810c50fb66f0fc65a1ceedc10309cf4b4013d64042a0331156
1. The attacker first borrowed a large amount of LST assets through flash loans, including (wstETH, rETH, WETH, 0xa35b_ETHx, rETH, wstETH, and cbETH).

Note that a portion of WETH was exchanged for ETH and then deposited into Tornado Cash, after which a withdrawal from Tornado Cash triggered the fallback function of the attack contract to execute the attack:


2. In the attack contract, first, the update_rates function of the yETH pool contract is called to update the corresponding rates for six types of LST assets and balance liquidity. Immediately after, 800 WETH is exchanged for LP tokens, namely yETH.

3. After that, the attacker performed a continuous operation of removing liquidity five times and then adding liquidity, where during the removal of liquidity, yETH would be burned and eight types of LST assets in the pool would be redeemed for the attacker based on weight; however, during the addition of liquidity, only liquidity of several assets would be added and liquidity of cbETH, wOETH, and mETH would not be added.

After the fifth liquidity addition, the total product of virtual balances (vb_prod) will be updated to 0, and the supply D value will be updated to a value close to the total virtual balance (vb_sum), which is larger than the normal expected value.
So why is this? Let us follow to the calcsupply function for analysis:

This function iteratively calculates the new supply amount in a loop, and in each iteration, it loops again 8 times to iteratively calculate the new product for the next iteration of supply calculation. The algorithm for calculating supply can be simplified to: s' = (l - s * r) / d, and the algorithm for calculating the product can be simplified to: r' = r * (s' / s)^8.
Through simulating the attack transaction steps with foundry, we find that the current supply (_supply) is approximately 2.51e21, the total virtual balance (_vb_sum) is approximately 1.0903e22, the product of virtual balances (_vb_prod) is approximately 3.5e15, and _amplification is a constant value of 4.5e20.
In the first cycle, s' = ((4.5e20 * 1.0903e22) - 2.51e21 * 3.5e15) / (4.5e20 - 1e18) ≈ 1.0927e22, r' = 3.5e15 * (1.09e22 / 2.5e21)^8 ≈ 4.57e20. In the second cycle, s' = ((4.5e20 * 1.0903e22) - 1.0927e22 * 4.57e20) / (4.5e20 - 1e18) ≈ 1.94e18. This triggers the core vulnerability of this attack; during the calculation of the new supply amount, the numerator uses the unsafe_sub function to subtract the values of l and s * r, and this function does not check for overflow, leading to the final calculated value underflowing to 1.94e18, which is far less than the previous supply amount.
When calculating the new product r', since s' is far smaller than s at this moment, the newly calculated total product of virtual balances (vb_prod) will be rounded down to equal 0.
Since the new product r' rounded down to 0, the value calculated for supply in all subsequent loops will always equal a constant value: s' = ((4.5e20 * 1.0903e22) - 0) / (4.5e20 - 1e18) ≈ 1.0927e22. The final calculated total supply of LP (approximately 1.0927e22) and total product of virtual balances (equal to 0) will be updated in the contract storage, and minting of yETH with the deviation for the attacker will occur.
Immediately after, the attacker again used cbETH to add unilateral liquidity under the conditions where the product of virtual balances and total supply were manipulated, resulting in these two liquidity additions minting an unexpected amount of yETH for the attacker.

4. Subsequently, the attacker restored the total product of virtual balances (vb_prod) to a non-zero value by removing liquidity (amounting to 0). First, let us follow the remove_liquidity function:

We can see that even when the incoming LP amount is 0 during liquidity removal, the total product of virtual balances will still be recalculated and updated, with the calculation formula being:

Where D is the total supply value, wi is the weight of each asset, and vbi is the new virtual balance of each asset after subtracting the redemption amount. Since the incoming LP amount is 0, it is still equal to the previous virtual balance. The final calculated total product of virtual balances (vb_prod) is approximately 9.09e19, and the total virtual balance and total supply remain unchanged, still equal to the previous value after unilateral liquidity addition (vb_sum ≈ 1.0926e22, supply ≈ 1.095e22).
The update_rates function is then called to update the exchange rate of wOETH assets, following to the update_rates function:

First, the latest exchange rate of wOETH will be obtained from the rate provider address. If the rate has changed, this new exchange rate will be used to update the total product of virtual balances (vb_prod), the corresponding virtual balance of assets, and the total virtual balance. After that, the updatesupply function will be called to update the total supply of LP. This explains why no liquidity was added to wOETH during the previous cycle of removing/adding liquidity; if the rate has changed, the updaterates function will be called during liquidity addition to update the rate. Only in this step is it necessary to use a rate difference to restore the manipulated vb_prod and supply.


The calc supply function will still be called to calculate the new supply amount and the total product of virtual balances. Since the virtual balance has been restored to a non-zero value through the above operations, the final calculated supply amount will be less than the previous supply amount, and the contract will burn this portion of yETH.

Thus, the values of the total product of virtual balances (vb_prod), total virtual balance (vb_sum), and total supply are updated to the following values: vb_prod ≈ 4.34e19, vb_sum ≈ 1.0926e22, supply ≈ 9.98e21.
5. Subsequently, the attacker redeemed all the LP tokens minted from the above two liquidity additions through the remove_liquidity function. Since this portion of LP was minted during the third step of amplifying manipulation of supply, and the redemption happened in the fourth step to restore and reduce supply, the attacker could redeem more tokens than expected, thereby reducing the asset balance and supply in the pool.

The attacker used the same method to perform cyclical manipulation again, gradually reducing the LP supply and virtual balances. Note that the rebase function of the OETHVaultCore contract was called, with the purpose of updating the exchange rate of wOETH, allowing the update_rates function to obtain new rates again and restore the values of the virtual total product and supply.
After the last manipulation, the attacker can directly empty all the assets in the pool after removing liquidity, making the total product of virtual balances (vb_prod), total virtual balance (vb_sum), and total supply all equal to 0.

6. In the case where the pool is completely emptied, the attacker begins to add dust liquidity to the empty pool:

Since the interest rates of the underlying eight LSD assets are close to 1e18, after adding a small amount of tokens, the virtual balance of each asset will directly equal the quantity of tokens. With a supply of 0, the internal calcvb_prod_sum function will be called to recalculate the total product of virtual balances (vb_prod), the total virtual balance, and the current total supply value during liquidity addition:


Immediately after, these values will be passed into the calcsupply function to calculate the new supply amount, which is the LP amount minted for the attacker. In the calcsupply function, during a certain iteration of the loop, the supply will also overflow due to unsafe_mul, resulting in a calculated supply of an enormous value (2.354e56), and minting the corresponding amount of yETH tokens for the attacker.

7. Finally, the attacker directly sold yETH for exchange through AMM and repaid the flash loan for profit.
MistTrack Analysis
According to on-chain tracking & anti-money laundering tool MistTrack analysis, the attacker profited approximately 9 million dollars in this incident, with initial funds coming from a small amount of ETH transferred from Railgun.

After the attacker initiated the attack, they first transferred 1,100 ETH into Tornado Cash, of which 100 ETH was withdrawn for further use:

Approximately 6 million dollars in profit funds (including 128 ETH, 48.96 cbETH, 203.55 rETH, 742.63 frxETH, 857.48 pxETH, 167.67 stETH) were concentrated and transferred to address 0xa80d3f2022f6bfd0b260bf16d72cad025440c822:

It is worth noting that subsequently, Yearn successfully recovered 2.4 million dollars by destroying the pxETH held by the hacker, and 857.48 pxETH has been re-minted and returned to the Redacted Cartel multi-signature wallet.

MistTrack has marked the relevant addresses and will continue to monitor the movement of funds.
Summary
The core of this attack lies in the fact that the attacker exploited the unsafe mathematical operations in the implementation logic of the yETH Weighted Stableswap Pool contract when adding liquidity, which led to overflow and rounding defects. By carefully constructing specific virtual balances and supply values to amplify the error caused by this defect, the attacker was able to mint massive amounts of LP tokens for profit. The Slow Mist security team recommends that project parties/auditors strengthen coverage testing for extreme scenarios and boundary conditions, and use safe mathematical operations and checks when calculating core variables to prevent serious vulnerabilities like overflow from affecting protocol security.
Reference
https://github.com/banteg/yeth-exploit/blob/main/report.pdf


