kaden.eth@0xKaden
yETH Exploit Deep Dive
After spending some time exploring the recent yETH exploit, I quickly realized that it's easily one of the most sophisticated attacks I've ever seen. In fact, it was so complicated that every writeup I read misunderstood at least some part of the attack. This complexity provides for some serious alpha to developers and security researchers who can thoroughly understand the attack, so don't just bookmark this, let's dive in.
Hybrid AMM Curve
To understand this exploit, we first need to understand the underlying mechanism of the protocol. The yETH pool uses an invariant which is a hybrid between constant product and constant sum. If you're familiar with the inner workings of Uniswap, you should be familiar with the constant product behavior, essentially it just adjusts the price according to the reserves. Whereas constant sum results in a constant price between the tokens, regardless of reserves. The yETH hybrid curve behaves like a constant sum when the token reserves are balanced, keeping the price constant, and behaves like a constant product curve when the reserves are imbalanced. This behavior is valuable for pools of assets which have the same value due to the fact that the price is much less sensitive to reserve changes.
Below we have a graph [1] of these different curves. Red: constant product, green: constant sum, blue: hybrid used by the yETH pool.
The First Bug: Breaking The Invariant
Let's zoom in on the `_calc_supply` function. This function uses an iterative approximation to converge to a new supply and constant product term at each iteration, ending the loop once sufficient precision is achieved.
The constant product term (r) is recomputed at each iteration as the current value multiplied by the new supply, divided by the previous supply (`r * sp / s`). Effectively, it scales at the same rate as the supply.
The bug: if the decrease in supply of any given iteration of the solver is large enough, the constant product term can round down to zero. There is no revert to handle this case and once it occurs, each following iteration will remain zero since `0 * x / y = 0`.
Now that we have a zero constant product term, we no longer have a hybrid constant product/constant sum curve, instead we effectively just have a constant sum curve. To understand why this is a problem we have to go back and look at the curves. In the below graph [2], we have the intended curve (red) and the constant sum curve (purple) which is the result of the zero product term.
As we adjust the supply (see desmos graph [2] linked in reply) of these two curves (D), we can see that the reserves increase by the same amount in the middle, where the reserves are balanced, but by different amounts on the outside, where the reserves are imbalanced. This means that as we add/remove liquidity with imbalanced reserves, these two curves will mint/burn a different amount of LP tokens.
Understanding this behavior, the attacker systemically switched between these curves by triggering the zero constant product term when adding liquidity with unbalanced reserves to receive more LP tokens than intended. They then resolved the constant product term back to normal during liquidity removal to receive the correct amount of tokens provided for burning the inflated amount of LP tokens they received. This allowed the attacker to withdraw more tokens than they deposited, which they repeated until the pool was drained of its reserves for a profit of about ~$8m.
The Second Bug: Unexpected Underflow
You thought we were done? Nope, there's yet another bug that the attacker exploited to steal even more funds after already completely draining the pool.
Now that the pool is empty, and variables used for accounting are in such an unusual state, there is a significant side effect which occurs when we attempt to deposit certain dust amounts. Again, looking in the `_calc_supply` function, when we iteratively recompute the supply, we compute it with the following line (`(l - s * r) / d`):
Since we use unchecked math here and the accounting is in a highly irregular state, it's unexpectedly possible for `s * r > l`, resulting in the computed supply underflowing. The attacker exploits this underflow by depositing the following amounts: `[1, 1, 1, 1, 1, 1, 1, 9]`, resulting in them being minted `~2.6*10^56` yETH LP tokens. The attacker then makes a swap on the curve yETH/WETH pool, draining the pool of its WETH, for a profit of ~$1m.
Conclusion
Not only did this attack include a highly sophisticated AMM invariant exploit, but it also exploited an underflow which is likely only possible due to the existence of the invariant exploit. This combination of exploits allowed the attacker to not only drain the yETH pool, but also another pool containing the LP token. Both attacks, and even tornado cash deposits were all made in the same transaction, preventing any chance at rescue.
In my research, every writeup I came across misunderstood this attack in some way. Clearly, it's extremely rare to understand such a sophisticated exploit, providing for some serious alpha to developers and security researchers to fully wrap their heads around this.