Platform: Code4rena
Start Date: 30/04/2024
Pot Size: $112,500 USDC
Total HM: 22
Participants: 122
Period: 8 days
Judge: alcueca
Total Solo HM: 1
Id: 372
League: ETH
Rank: 38/122
Findings: 2
Award: $101.32
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: guhu95
Also found by: 0rpse, 0x007, 0x73696d616f, 0xCiphky, 0xabhay, Audinarey, Bauchibred, Fassi_Security, GalloDaSballo, GoatedAudits, KupiaSec, LessDupes, MSaptarshi, OMEN, Ocean_Sky, RamenPeople, SBSecurity, Tendency, WildSniper, aslanbek, bill, blutorque, crypticdefense, cu5t0mpeo, d3e4, gjaldon, grearlake, gumgumzum, honey-k12, ilchovski, jokr, josephdara, kennedy1030, p0wd3r, peanuts, stonejiajia, t0x1c, tapir, underdog, zzykxx
0.4071 USDC - $0.41
https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Withdraw/WithdrawQueue.sol#L246 https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Withdraw/WithdrawQueue.sol#L299-L309
Claiming withdrawals may change the ezETH price. This can be used extract profit from the protocol, at the cost of other holders.
The price of ezETH is essentially TVL / totalSupply
, i.e. depositing d
worth of collateral will mint d * totalSupply / TVL
ezETH (RenzoOracle.calculateMintAmount()
). When requesting to withdraw w
ezETH the reverse calculation is made, i.e. w * TVL / totalSupply
(RenzoOracle.calculateRedeemAmount()
). These values are set at the time of the withdrawal request, such that the amount of collateral token and amount of ezETH to burn are fixed. Only later upon claim()
are the tokens transferred and the ezETH burned.
The values used in the deposit calculation are also not affected by any pending withdrawals, but the current TVL and the current ezETH supply are used.
Suppose the value of the collateral token to be withdrawn has deviated from the average collateral value, i.e. the price of ezETH, since the withdrawal request was made. If it is greater than the average then upon claim()
correspondingly more value will be removed from the TVL than ezETH burned from the total supply, and the price of ezETH will decrease. Conversely, if the value of the collateral token has become less than the average of the collateral values then the price of ezETH will increase.
This can be abused in all kinds of ways. It gives an attacker some measure of control over the price of ezETH. He can make withdrawal requests and then choose to claim them such that the price is manipulated in his favour, depending on the integrations with Renzo. The attacker can frontrun another claim (not necessarily his own) with a deposit such that he experiences an immediate profit from the price movement.
For example, let's say 2
ezETH has been minted against 1
tokA and 1
tokB, and that all current prices are 1
. Suppose 0.5
ezETH is requested to be withdrawn against tokA. This will schedule the burning of 0.5
ezETH and the transfer of 0.5
tokA. Suppose then the price of tokA increases to 1.1
and the price of tokB increases to 1.25
. Before claim()
the price of ezETH is (1.1 + 1.25) / 2 = 1.175
, but after claim()
the price would be (0.55 + 1.25) / 1.5 = 1.2
. Now, if an attacker deposits 1
ETH's worth (of any token) just before claim()
is called, he will receive 1 / 1.175 ≈ 0.8512
ezETH. After claim()
the price will then be (0.55 + 1.25 + 1) / 2.3512 ≈ 1.1910
. Immediately withdrawing this again would give him 0.8512 * 1.1910 ≈ 1.014
ETH worth of collateral.
Note that by sandwiching the claim()
with a deposit and withdrawal the attacker was able to reduce the price of ezETH (up to future additional price changes) and extract a profit.
The more volatile the collateral tokens, and especially if they move in opposite directions, the more profit can be extracted.
Consider calculating the amount of collateral token to be transferred at the time of claim()
instead. It could then be possible to also choose which token to withdraw from in claim()
. That is, a withdraw request would simply be a request to withdraw an amount of ezETH, with the rest to be decided upon claim()
.
MEV
#0 - c4-judge
2024-05-16T10:57:45Z
alcueca marked the issue as satisfactory
#1 - c4-judge
2024-05-16T11:01:18Z
alcueca marked the issue as duplicate of #326
🌟 Selected for report: GalloDaSballo
Also found by: 0xabhay, GoatedAudits, SBSecurity, d3e4, jokr, p0wd3r, peanuts, zhaojohnson
100.9059 USDC - $100.91
https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/RestakeManager.sol#L507 https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Withdraw/WithdrawQueue.sol#L229
A discrepancy in oracle price deviation between two assets can be exploited for arbitrage; one can deposit the relatively overvalued asset and withdraw for the relatively undervalued asset.
The price of ezETH is based on a sum of oracle prices of multiple assets. The oracles will be slightly inaccurate; in the case of the Chainlink stETH/ETH feed the deviation threshold is 0.5%, which means that it may be up to 0.5% more or less than the true price. The deposit is valued according to the oracle price, and so is the amount of tokens received on withdrawal.
Suppose 2000
ezETH has been minted against 1000
tokA and 1000
tokB, and that the current true prices of tokA and tokB are 1
. However, suppose the oracle returns a price of tokA as 1.005
and of tokB as 0.997
. This means that ezETH will be priced at (1005 + 997) / 2000 = 1.001
An attacker can exploit this by depositing tokA and withdrawing tokB. Suppose the attacker buys 100
tokA for 100
ETH (according to its true price) and deposits this. This will be valued as 100 * 1.005 = 100.5
ETH by the oracle and he will get 100.5 / 1.001 ≈ 100.4
ezETH. (This does not change the price of ezETH.) If he then immediately withdraws the ezETH, but now for tokB, he will get a value of 100.4 * 1.001 = 100.5
ETH, which in tokB is–according to the oracle–100.5 / 0.997 ≈ 100.8
tokB, which is also truly worth 100.8
ETH.
The attacker was thus able to acquire 100.8
ETH worth of tokB by depositing only 100
ETH worth of tokA.
Note that this attack depends on a differential deviation between assets. It would not work if there was only one asset, or if they deviate identically. The maximum extractable value in percentage is the maximum possible deviation between any two assets. Consider eliminating the profitability of this attack by taking exactly this percentage as a fee on either deposit of withdrawal, e.g. if the deviation threshold of tokA is 0.5% and of tokB is 0.3% then charge a 0.8% fee. This fee might either be taken by the protocol or just implemented as a reduced amount of ezETH minted or tokens withdrawn, effectively returning it to other holders.
MEV
#0 - c4-judge
2024-05-17T13:42:40Z
alcueca marked the issue as duplicate of #326
#1 - c4-judge
2024-05-17T13:42:58Z
alcueca marked the issue as satisfactory
#2 - d3e4
2024-05-26T20:11:48Z
@alcueca The root cause of #326 is the pricing of withdrawals on withdraw
instead of on the delayed claim
(e.g. here and here). Note my other issue #239 which is correctly duplicated to #326 on this basis.
This issue (#424), on the other hand, is not based on either the pricing on withdraw
instead of claim
, nor the delay, nor on sandwiching an oracle update. Rather, it is a static issue that arises when tokenizing a sum of price feeds. It can only be mitigated by a pessimistic valuation, effectively charging a fee on withdrawal.
The originary report of this kind of issue might be helpful (note however that it also deals with the additional complication that it was not possible to freely choose which asset to deposit or withdraw).
#3 - c4-judge
2024-05-27T11:46:31Z
alcueca marked the issue as not a duplicate
#4 - alcueca
2024-05-27T11:48:43Z
@jatinj615, please review. You might consider this expected behaviour, but same as #13 arbitrage opportunities of this kind should be avoided. I also see fees on withdrawals as a more elegant and doable solution to prevent MEV and arbitrage than pricing withdrawals upon claiming.
#5 - c4-judge
2024-05-28T04:19:47Z
alcueca marked the issue as primary issue
#6 - jatinj615
2024-05-29T14:25:44Z
@alcueca , thanks for pointing out the arbitrage issue. Some pointers which needs to be considered at the current state of the protocol from our side -
true exchange rate
from the contract. through WBETHShim contract. Hence, only stETH oracle feed is volatile.Acknowledging for now.
#7 - c4-judge
2024-05-30T06:08:33Z
alcueca marked the issue as selected for report
#8 - c4-judge
2024-05-30T06:15:54Z
alcueca marked the issue as duplicate of #13
#9 - c4-judge
2024-05-30T06:15:58Z
alcueca marked the issue as not selected for report
#10 - alcueca
2024-05-30T06:16:26Z
Given that the root cause is the use of market rates for stEth, this becomes a uplicate of #13
#11 - c4-judge
2024-05-30T06:29:10Z
alcueca changed the severity to 2 (Med Risk)
#12 - d3e4
2024-05-30T13:36:44Z
@alcueca Why was this lowered to Medium and duplicated to #13 (/deselected as best)?
I'm not sure if this has been properly understood, but #424 has nothing to do with frontrunning an oracle update. #429 is only based on frontrunning an oracle update, and #13 is only based on frontrunning an update of an stETH depeg. #424, on the other hand, exploits the ever present deviations in the oracle price from the true market price (as acknowledged by the Chainlink oracle).
The root cause of #424 is not the use of the market rate; rather, being able to use the true market rate would completely eliminate the arbitrage discussed in #424.
#13 is quite vaguely written, but it seems to lack any proof to distinguish it from #429. The first part of its PoC is just a sequence of trades stETH -> ezETH
, price change
, ezETH -> stETH
, just as reported by #429. The second part seems to be essentially the same thing: ETH -> ezETH
, price change
, ezETH -> stETH
. Only by invoking a second asset could it be connected to #424, in which case it has to be interpreted that "stETH market depegs" is not reflected in the oracle price feed. This information is missing, however.
It turns out the price change in ezETH can only effectively happen if it is based on more than a single underlying asset, otherwise the price of ezETH would just be a fictitious conversion factor (as long as the vault is otherwise fairly implemented). However, #13 and #429 make no mention of, nor seem to be aware, this fact; they simply seem to be based on the idea of the price's changing and to exploit this as a possibility to predict the "future" for a winning trade.
#13 and #429 can be said to describe an arbitrage over time, which is not really an arbitrage in the usual sense but a regular trade. The issue in this context is that because of the oracle price update and mempools we can exploit this to predict the future, which is not possible in regular real-time trading. #424 instead describes an arbitrage between two underlying assets. Because of the oracle price deviation the multiple asset vault enables a true arbitrage due to mispricing between its assets. This mispricing is generally present, and whenever two assets are mispriced in opposite ways there is an immediate and unavoidable arbitrage opportunity between them which will persist (at least) until the oracle price updates.
#13 - alcueca
2024-05-30T19:40:59Z
All these issues are quite similar in that there is an arbitrage opportunity in the way that ezETH is minted based on oracle prices, which invariably deviate from the true value of ezETH, whatever that might be.
#13 shows that using the stETH/ETH market price deviates from the stETH/ETH exchange rate price. #428, #429, and #613 show that the stETH/ETH oracle price deviates from the market price. #424, #425, #246 and #247 show that the ezETH/ETH price on deposit deviates from the market price of the collaterals used to mint it.
All these cases are mitigated, at least partially, by locking in the withdrawals queue the LSTs without producing yield for the cooldown period. All these cases would be mitigated by applying deposit or withdrawal fees. Several other mitigations, like delays on deposits, or pricing withdrawals at the time of claim, would likely address all of these findings equally.
Based on this, and how all the findings seem to draw from the same past contests and protocols, I'm inclined to consider them a group.
I also have the impression that the sponsor was not unaware of these issues, but that probably didn't consider them to be serious enough to warrant more serious defensive measures. None of these issues has proven beyond doubt that they would have a serious impact on the protocol. Based on that, I think that a Medium severity is fair.