Renzo - d3e4's results

A protocol that abstracts all staking complexity from the end-user and enables easy collaboration with EigenLayer node operators and a Validated Services (AVSs).

General Information

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

Renzo

Findings Distribution

Researcher Performance

Rank: 38/122

Findings: 2

Award: $101.32

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

0.4071 USDC - $0.41

Labels

bug
3 (High Risk)
satisfactory
sufficient quality report
:robot:_00_group
duplicate-326

External Links

Lines of code

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

Vulnerability details

Impact

Claiming withdrawals may change the ezETH price. This can be used extract profit from the protocol, at the cost of other holders.

Proof of Concept

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().

Assessed type

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

Findings Information

🌟 Selected for report: GalloDaSballo

Also found by: 0xabhay, GoatedAudits, SBSecurity, d3e4, jokr, p0wd3r, peanuts, zhaojohnson

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sponsor acknowledged
sufficient quality report
:robot:_57_group
duplicate-13

Awards

100.9059 USDC - $100.91

External Links

Lines of code

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

Vulnerability details

Impact

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.

Proof of Concept

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.

Assessed type

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 -

  • Only stETH collateral uses the exchange rate feed while wbETH uses the true exchange rate from the contract. through WBETHShim contract. Hence, only stETH oracle feed is volatile.
  • while stETH exchange rate can potentially open up an arbitrage opportunity. There are few factors which I would like to highlight -
  1. collateral amount gets locked in the withdrawQueue for atleast 7 days. Earning 0 yield on the collateral. technically loosing 7-8 bps of yield at 4% apy.
  2. Protocol will be having lower withdraw buffer for LST compared to ETH withdraw buffer so minimize the impact. And if huge deviation is monitored then admin will not fill up the withdraw buffer until the feed has stabilised. to minimize the arb value.
  • While I understand fee on withdrawals seems like a doable solution, it impacts the user's APY which is not favourable for protocol users. Instead we think a long term solution can be implemented by utilising the true price and exchange rate price of stETH. i.e. consider lowest of both at time of deposit and highest of both at withdraw.

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.

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter