code-423n4 / 2024-04-renzo-validation

2 stars 2 forks source link

Sudden decreases in TVL can be gamed to avoid losses #678

Closed c4-bot-1 closed 5 months ago

c4-bot-1 commented 5 months ago

Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/main/contracts/Withdraw/WithdrawQueue.sol#L220-L224

Vulnerability details

The way the protocol is designed allows ezETH holders to avoid incurring into losses when the protocol TVL has a instant decrease. In Renzo a sudden TVL decrease might happen for multiple (mostly unavoidable) reasons:

To avoid the loss, a staker can withdraw his ezETH via WithdrawQueue::withdraw() before the loss is reflected in the protocol TVL, this is possible because WithdrawQueue::withdraw() calculates and caches the amount of ETH/Tokens to redeem based on the current value of the ezETH, which doesn't account for losses yet:

...SNIP...
@> uint256 amountToRedeem = renzoOracle.calculateRedeemAmount(
   _amount,
   ezETH.totalSupply(),
   totalTVL
);
...SNIP...
// add withdraw request for msg.sender
withdrawRequests[msg.sender].push(
    WithdrawRequest(
        _assetOut,
        withdrawRequestNonce,
@>      amountToRedeem,
        _amount,
        block.timestamp
    )
);
...SNIP...

After a time delay, the staker can call WithdrawQueue::claim() to claim the amount of assets calculated during the execution of WithdrawQueue::withdraw(), which is unaffected by the losses.

Important to note that, in case of penalties/slashings of validators controlled by Renzo both functions used to notify Renzo of the loss (EigenPod::verifyAndProcessWithdrawals() and/or EigenPod::verifyBalanceUpdates()) are permissionless, which allows a staker to avoid the loss by executing the calls atomically.

Impact

Stakers can avoid being affected by instant TVL drops, this will also make fair users incur into more losses than they should.

Proof of Concept

Alice, a staker in the Renzo protocol, is monitoring the beacon chain and notices a validator is being slashed:

  1. Alice calls WithdrawQueue::withdraw() to withdraw all of her ezETH
  2. Alice (or a third party) calls EigenPod::verifyAndProcessWithdrawals(), Renzo TVL drops instantly.
  3. Alice calls WithdrawQueue::claim() and redeems her ezETH, being unaffected by the slashing

Recommended Mitigation Steps

The amount of ETH/Tokens to be claimed should be calculated in WithdrawQueue::claim() instead of WithdrawQueue::withdraw(). This way the funds that are currently queued for withdrawals will be still subject to losses.

Assessed type

Other

DadeKuma commented 5 months ago

@howlbot accept