xanoutas/solidity-security-findings huggingface dataset:
Stakers can avoid slashing penalties by withdrawing
Audit: 2024-04-renzo-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
When validator penalties occur, a call to the respective `Eigenpod` is made via [Eigenpod::verifyBalancesUpdates()](https://github.com/Layr-Labs/eigenlayer-contracts/blob/dev/src/contracts/pods/EigenPod.sol#L188) or [Eigenpod::verifyAndProcessWithdrawals()](https://github.com/Layr-Labs/eigenlayer-contracts/blob/dev/src/contracts/pods/EigenPod.sol#L235).

A user of `Renzo` protocol can front-run this transaction and immediately call `WithdrawalQueue::withdraw` to avoid this penalty. This works if the `buffer` has enough funds to process with withdrawal.

## Proof of Concept
As mentioned, a validator can experience penalties and users can know ahead of time by front-running the calls that are made when penalties occur, such as `Eigenpod::verifyBalancesUpdates`.

The user can proceed to call `WithdrawalQueue::withdraw`
https://github.com/code-423n4/2024-04-renzo/blob/main/contracts/Withdraw/WithdrawQueue.sol#L206
```javascript
   /**
     * @notice  Creates a withdraw request for user
     * @param   _amount  amount of ezETH to withdraw
     * @param   _assetOut  output token to receive on claim
     */
    function withdraw(uint256 _amount, address _assetOut) external nonReentrant {
        // check for 0 values
        if (_amount == 0 || _assetOut == address(0)) revert InvalidZeroInput();

        // check if provided assetOut is supported
        if (withdrawalBufferTarget[_assetOut] == 0) revert UnsupportedWithdrawAsset();

        // transfer ezETH tokens to this address
        IERC20(address(ezETH)).safeTransferFrom(msg.sender, address(this), _amount);

        // calculate totalTVL
        (, , uint256 totalTVL) = restakeManager.calculateTVLs();

        // Calculate amount to Redeem in ETH
        uint256 amountToRedeem = renzoOracle.calculateRedeemAmount(
            _amount,
            ezETH.totalSupply(),
            totalTVL
        );

        // update amount in claim asset, if claim asset is not ETH
        if (_assetOut != IS_NATIVE) {
            // Get ERC20 asset equivalent amount
            amountToRedeem = renzoOracle.lookupTokenAmountFromValue(
                IERC20(_assetOut),
                amountToRedeem
            );
        }

        // revert if amount to redeem is greater than withdrawBufferTarget
@>      if (amountToRedeem > getAvailableToWithdraw(_assetOut)) revert NotEnoughWithdrawBuffer();

        // increment the withdrawRequestNonce
        withdrawRequestNonce++;

        // add withdraw request for msg.sender
        withdrawRequests[msg.sender].push(
            WithdrawRequest(
                _assetOut,
                withdrawRequestNonce,
                amountToRedeem,
                _amount,
                block.timestamp
            )
        );
Users can avoid slashes and penalties
Audit: 2024-04-renzo-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/1c7cc4e632564349b204b4b5e5f494c9b0bc631d/contracts/RestakeManager.sol#L329


# Vulnerability details

## Description
Operators are susceptible to [slashing and/or penalties](https://consensys.io/blog/understanding-slashing-in-ethereum-staking-its-importance-and-consequences) on an Execution Layer level.
EigenLayer has two functions that can be called by anyone to update the state whenever these two events happen:
- [verifyBalanceUpdates](https://github.com/Layr-Labs/eigenlayer-contracts/blob/dev/src/contracts/pods/EigenPod.sol#L188-L224)
- [verifyAndProcessWithdrawals](https://github.com/Layr-Labs/eigenlayer-contracts/blob/dev/src/contracts/pods/EigenPod.sol#L235-L280)

Both these functions leads to a change in [podOwnerShares](https://github.com/Layr-Labs/eigenlayer-contracts/blob/dev/src/contracts/pods/EigenPodManager.sol#L118), which in turn results in the `TVL` of Renzo changing because the variables `podOwnerShares` is used in here:

- [RestakeManager.sol#L329](https://github.com/code-423n4/2024-04-renzo/blob/1c7cc4e632564349b204b4b5e5f494c9b0bc631d/contracts/RestakeManager.sol#L329)
- [OperatorDelegator.sol#L338-L345](https://github.com/code-423n4/2024-04-renzo/blob/1c7cc4e632564349b204b4b5e5f494c9b0bc631d/contracts/Delegation/OperatorDelegator.sol#L338-L345)
```javascript
contract RestakeManager {
	uint256 operatorEthBalance = operatorDelegators[i].getStakedETHBalance();
}

contract OperatorDelegator{
	function getStakedETHBalance() external view returns (uint256) {
	        // accounts for current podOwner shares + stakedButNotVerified ETH + queued withdraw shares
	        int256 podOwnerShares = eigenPodManager.podOwnerShares(address(this));
	        return
	            podOwnerShares < 0
	                ? queuedShares[IS_NATIVE] + stakedButNotVerifiedEth - uint256(-podOwnerShares)
	                : queuedShares[IS_NATIVE] + stakedButNotVerifiedEth + uint256(podOwnerShares);
	    }
}
```
This means that a user can front-run any slashing/penalty and withdraw his full amount while the rest of the stakers get punished.
## Proof of Concept
- Alice has `1000 ezETH`.
- Alice monitors the mempool and sees that some of the validators that the operator uses is being slashed.
- Alice front-runs the `verifyBalanceUpdates()` call with a `withdraw()` call.
- Alice is now able to safely withdraw the full amount of `1000 ezETH`, before the `verifyBalanceUpdates()` function succeeds and results in a TVL drop.

## Tools used
Manual Review

## Recommended Mitigation Steps
Handle the slashing differently.


## Assessed type

Other
Stakers can avoid slashing and penalties while making next claimers take the loss
Audit: 2024-04-renzo-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/main/contracts/RestakeManager.sol#L274-L358
https://github.com/code-423n4/2024-04-renzo/blob/main/contracts/Delegation/OperatorDelegator.sol#L338-L345


# Vulnerability details

## Impact

Depositors in the `Renzo` system can avoid penalties and slashing and make the next withdrawers take the loss by requesting a withdrawal and then updating the effective balance of the validator.

## Proof of Concept

Validators can lose part of their deposit via [penalties](https://eth2book.info/capella/part2/incentives/penalties/) or [slashing](https://eth2book.info/capella/part2/incentives/slashing/) events. 

It is important to note that `Renzo` as a staker itself is not incentivized to call `EigenPod::verifyBalanceUpdates`, since there is a chance the `podOwnerShares` that are included in the calculations of `calculateTVL`, to be decreased, which will decrease the price of `ezETH`. 

But now users, **without any risk for themselves**, can make the next withdrawers take the loss of slashing and penalties by first initiating withdrawal from the `WithdrawQueue::withdraw` and then calling `EigenPod::verifyBalanceUpdates` on behalf of the penalized `OperatorDelegators` or simply frontrun another user’s transactions which will apply the slashing penalty.

The issue is that nowhere when a withdrawal request is initiated there is a check or update on whether one or more of the validators have their `podOwnerShares` decreased which will be the case when the Delegator validator (EigenPod) has been slashed.

Here is the function that calculates the TVL in the `Renzo` protocol:

[RestakeManager.sol#L274-L358](https://github.com/code-423n4/2024-04-renzo/blob/main/contracts/RestakeManager.sol#L274-L358)

```solidity
function calculateTVLs() public view returns (uint256[][] memory, uint256[] memory, uint256) {
    uint256[][] memory operatorDelegatorTokenTVLs = new uint256[][](operatorDelegators.length);
    uint256[] memory operatorDelegatorTVLs = new uint256[](operatorDelegators.length);
    uint256 totalTVL = 0;

    // Iterate through the ODs
    uint256 odLength = operatorDelegators.length;

    for (uint256 i = 0; i < odLength; ) {
...MORE CODE
        // Track the TVL for this OD
        uint256 operatorTVL = 0;

        // Get the value of native ETH staked for the OD
        uint256 operatorEthBalance = operatorDelegators[i].getStakedETHBalance();//@audit calculates effective balance of validator

        // Save it to the array for the OD
        operatorValues[operatorValues.length - 1] = operatorEthBalance;

        // Add it to the total TVL for this OD
        operatorTVL += operatorEthBalance;

        // Add it to the total TVL for the protocol
        totalTVL += operatorTVL;

        // Save the TVL for this OD
        operatorDelegatorTVLs[i] = operatorTVL;
...MORE CODE
}
```

And the function that is responsible for getting the manipulatable values is `OperatorDelegator::getStakedETHBala
Stakers can frontrun slashing and avoid validator penalties
Audit: 2024-04-renzo-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Withdraw/WithdrawQueue.sol#L206-L263


# Vulnerability details

## Impact

Stakers can avoid penalties, and other stakers have to bear the burden of the penalty.

## Proof of Concept

When depositing native token or LST to EigenLayer, the operators are subject to slashing conditions. When slashing happens, it will affect the total value of the collateral token in the operator delegator.

```
function calculateTVLs()

                // Get the value of this token

                uint256 operatorBalance = operatorDelegators[i].getTokenBalanceFromStrategy(
                    collateralTokens[j]
                );
```

This will affect the ezETH calculation conversion (a smaller TVL means lesser redeem amount).

```
        // Calculate amount to Redeem in ETH
        uint256 amountToRedeem = renzoOracle.calculateRedeemAmount(
            _amount,
            ezETH.totalSupply(),
            totalTVL
        );
```
```
 function calculateRedeemAmount(
        uint256 _ezETHBeingBurned,
        uint256 _existingEzETHSupply,
        uint256 _currentValueInProtocol
    ) external pure returns (uint256) {
        // This is just returning the percentage of TVL that matches the percentage of ezETH being burned
>        //@audit -> lower _currentValueInProtocol = lower redeemAmount
>        uint256 redeemAmount = (_currentValueInProtocol * _ezETHBeingBurned) / _existingEzETHSupply;

        // Sanity check
        if (redeemAmount == 0) revert InvalidTokenAmount();

        return redeemAmount;
    }
```

When users know that the validator is going to get slashed, they can frontrun the slashing event by withdrawing their ezETH. When `withdraw()` is called, a `withdrawRequest` will be created for the user.

```
 function withdraw(uint256 _amount, address _assetOut) external nonReentrant {
 // add withdraw request for msg.sender
        withdrawRequests[msg.sender].push(
            WithdrawRequest(
                _assetOut,
                withdrawRequestNonce,
                amountToRedeem,
                _amount,
                block.timestamp
            )
        );
```

After 7 days, the user can claim the same amount that is recorded in the `withdrawRequest` struct. 

```
 function claim(uint256 withdrawRequestIndex) external nonReentrant {
 // send selected redeem asset to user
        if (_withdrawRequest.collateralToken == IS_NATIVE) {
            payable(msg.sender).transfer(_withdrawRequest.amountToRedeem);
        } else {
            IERC20(_withdrawRequest.collateralToken).transfer(
                msg.sender,
                _withdrawRequest.amountToRedeem
            );
        }
        // emit the event
        emit WithdrawRequestClaimed(_withdrawRequest);
```

Other users that did not frontrun the slashing will have to bear the burden of the value of ezETH going down.

## Tools Used

Manual Review

## Recommended Mi
DepositQueue can be sandwiched to extract more value from ezETH
Audit: 2024-04-renzo-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/RestakeManager.sol#L592-L616


# Vulnerability details

## Impact

Users will get more ezETH than intended.

## Proof of Concept

When depositing ETH via `restakeManager.depositETH()`, the function first calculates the total value locked TVL of the protocol.

```
    function depositETH(uint256 _referralId) public payable nonReentrant notPaused {
        // Get the total TVL
        (, , uint256 totalTVL) = calculateTVLs();


        // Enforce TVL limit if set
        if (maxDepositTVL != 0 && totalTVL + msg.value > maxDepositTVL) {
            revert MaxTVLReached();
        }
```

This TVL is counted by getting the value from the operatorDelegator contract through `getTokenBalanceFromStrategy()` and `getStakedETHBalance()`, which gets its value from Eigenlayer contract. The TVL also includes the value locked in the withdrawal queue, this includes the deposit buffer and the amount waiting to be withdrawn by users.

What is not included is the value locked in the DepositQueue. Only when 32 ETH is locked in the depositQueue, it will then be transferred to EigenLayer with the owner calling `stakeETHFromQueue()` in the DepositQueue contract, but before that, it will stay idle in the DepositQueue contract.

```
  function stakeEthFromQueue(
        IOperatorDelegator operatorDelegator,
        bytes calldata pubkey,
        bytes calldata signature,
        bytes32 depositDataRoot
    ) external onlyNativeEthRestakeAdmin {
        uint256 gasBefore = gasleft();
        // Send the ETH and the params through to the restake manager
        restakeManager.stakeEthInOperatorDelegator{ value: 32 ether }(
            operatorDelegator,
            pubkey,
            signature,
            depositDataRoot
        );

        emit ETHStakedFromQueue(operatorDelegator, pubkey, 32 ether, address(this).balance);

        // Refund the gas to the Admin address if enough ETH available
        _refundGas(gasBefore);
    }
```

The `depositETH()` function will then deposit the amount of ETH into the depositQueue and calculate the mint amount. 

When withdrawing tokens, the TVL is calculated and the ezETH amount is exchanged to the requested token, with the conversion rate calculated through `renzoOracle.calculateRedeemAmount`. Note the higher the `_currentValueInProtocol`, the larger the `redeemAmount`.

```
 function calculateRedeemAmount(
        uint256 _ezETHBeingBurned,
        uint256 _existingEzETHSupply,
        uint256 _currentValueInProtocol
    ) external pure returns (uint256) {
        // This is just returning the percentage of TVL that matches the percentage of ezETH being burned
>       uint256 redeemAmount = (_currentValueInProtocol * _ezETHBeingBurned) / _existingEzETHSupply;

        // Sanity check
        if (redeemAmount == 0) revert InvalidTokenAmount();

        return redeemAmount;
    }
```

What the user can do is that wh
Execution layer rewards can be sandwiched
Audit: 2024-04-renzo-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Withdraw/WithdrawQueue.sol#L206-L263
https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Rewards/RewardHandler.sol#L58-L60


# Vulnerability details

## Impact
When the Renzo validators earns execution layer rewards, the rewards will be sent to the RewardHandler contract. Since these rewards are not a traditional eth transfer the receive function will not automatically send the rewards to deposit queue. Instead, all the rewards will be accumulated and the admin will forward them to deposit queue. Anyone can deposit ETH before the rewards sent to deposit queue and immediately withdraw to pocket an atomic profit. Though the funds has to be claimed delayed because of the 2 step withdrawal process in Renzo. 
## Proof of Concept
Since the attack vector is simple I will not provide a coded poc but explain textually. 

Assume there are 1 ETH rewards which comes from the execution layer rewards that the Renzo validators run. These rewards are currently standing idle in RewardHandler contract waiting for admin to call forwardRewards. 

When attacker sees the `forwardRewards` function call, attacker quickly deposits some ether to Renzo before the `forwardRewards` tx. 
Then right after the `forwardRewards` tx, attacker starts withdrawal process by calling `withdraw`. Since the TVL increased when the `forwardRewards` tx placed in, attacker will receive more ether in return when the cooldown period is over.
## Tools Used

## Recommended Mitigation Steps
I think there can't be an easy code solution to this. 
Don't let rewards accumulate too much such that sandwich is not attractable. 





## Assessed type

MEV
A part of ETH rewards can be stolen by sandwiching 
Audit: 2024-04-renzo-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Delegation/OperatorDelegator.sol#L503


# Vulnerability details

## Impact
If the protocol receives any ETH, from Eigen Layer maybe as reward it is sent for fullWithdrawl via the DepositQueue contract after filling the withdraw buffer.

The withdrawQueue contract get filled by 3 ways ->
New Deposits
Daily Rewards Coming in the Protocol.
Manual withdrawal from EigenLayer. Permissioned call from OperatorDelegator.

So for rewards coming in to the protcol users can claim those by 
calling the withdraw -> claim

An attacker can take advantage of this to steal a part of the rewards:

Mint a sensible amount of LRTTokens by depositing an accepted asset
Call `WithdawlQueue.withdraw`, after which the value of the LRTTokens just minted will immediately increase.
then claim those by `claim`

## Proof of Concept
https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Withdraw/WithdrawQueue.sol#L206

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Withdraw/WithdrawQueue.sol#L279

Here it only checks the cooldown period with the time when `withdraw` req was initiated for any type of withdrawl, which makes the possibility for sandwiching the rewards.

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Withdraw/WithdrawQueue.sol#L287

However the amount required to deposit for minting ezETH must be very high to disrupt the price ,since there is a presence of deposit buffer which might make it more costly

## Tools Used
Manual Review

## Recommended Mitigation Steps
Making the distribution of rewards in a delayed manner


## Assessed type

Error
Reward distribution can be sandwich to steal other stakers rewards 
Audit: 2024-04-renzo-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Deposits/DepositQueue.sol#L158-L163


# Vulnerability details

## Impact

When the protocol receive rewards from the EigenLayer, those rewards for ex. native ETH is channelled to the DepositQueue contract through the rewardHandler, increasing the totalTVL currently hold by the protocol. Since ezETH supply is backed by the totalTVL, any ezETH token holder receive more value for their shares as the more value get added to the protocol. 

Before rewards get add to the totalTVL, an attacker could take advantage by frontrunning it with the deposit txn in RestakeManager(which mints ezETH token shares to the attacker) , and immediately backrunning it with `withdrawQueue::withdraw()` request. 

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Withdraw/WithdrawQueue.sol#L220-L224
```solidity
    function withdraw(uint256 _amount, address _assetOut) external nonReentrant {
        // transfer ezETH tokens to this address
        IERC20(address(ezETH)).safeTransferFrom(msg.sender, address(this), _amount);

        // calculate totalTVL
        (, , uint256 totalTVL) = restakeManager.calculateTVLs();

        // Calculate amount to Redeem in ETH
        uint256 amountToRedeem = renzoOracle.calculateRedeemAmount(
            _amount,
            ezETH.totalSupply(),
            totalTVL
        );
	...snip...
    }
```


Notice the `amountToRedeem` above calculated from the new `totalTVL`. As a result, attacker manage to steal more value out of the protocol, griefing the other stakers with less rewards. 
## Proof of Concept
https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Oracle/RenzoOracle.sol#L157-L158

## Tools Used
Manual review
## Recommended Mitigation Steps
An epoch system could be built around withdrawQueue, where the ezETH holders eligible for new rewards if only if they have staked previously. 


## Assessed type

MEV
User's Rewards Can Be Sandwiched
Audit: 2024-04-renzo-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Delegation/OperatorDelegator.sol#L405-L424


# Vulnerability details

## Impact
The `OperatorDelegator` can receive rewards through a partial withdrawal upon invoking the [OperatorDelegator::verifyAndProcessWithdrawals](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Delegation/OperatorDelegator.sol#L405-L424) function, or when a validator exists and the excess of 32 ether is claimed by the OperatorDelegator as a reward on call to the [OperatorDelegator::verifyAndProcessWithdrawals](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Delegation/OperatorDelegator.sol#L405-L424) function, for a full withdrawal.

**DETAILS**:
       When [OperatorDelegator::verifyAndProcessWithdrawals](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Delegation/OperatorDelegator.sol#L405-L424) function is called, the function will go on to make an external call to [EigenPod::verifyAndProcessWithdrawals](https://github.com/Layr-Labs/eigenlayer-contracts/blob/90a0f6aee79b4a38e1b63b32f9627f21b1162fbb/src/contracts/pods/EigenPod.sol#L235-L280) function, which after computation, on either instance mentioned above(partial or full withdrawal, where on full withdrawal the amount set to be withdrawn is above the max stake), the rewards are scheduled to be sent as a delayed withdrawal to the OperatorDelegator contract via [DelayedWithdrawalRouter::createDelayedWithdrawal](https://github.com/Layr-Labs/eigenlayer-contracts/blob/90a0f6aee79b4a38e1b63b32f9627f21b1162fbb/src/contracts/pods/DelayedWithdrawalRouter.sol#L68-L91) function, whose current delay is 7 days.

The issue here is that, an attacker can take advantage of the sudden change in the contract total value locked to extract value.
After 7 days, the attacker can:

+ mint ezETH via a call to [RestakeManager::depositETH](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/RestakeManager.sol#L582-L584) function
+ Call  Eigen Layer [DelayedWithdrawalRouter::claimDelayedWithdrawals()](https://github.com/Layr-Labs/eigenlayer-contracts/blob/90a0f6aee79b4a38e1b63b32f9627f21b1162fbb/src/contracts/pods/DelayedWithdrawalRouter.sol#L100-L105) function to send the scheduled withdrawal to the OperatorDelegator contract.
+ Call [WithdrawQueue::withdraw](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Withdraw/WithdrawQueue.sol#L206-L263) function, to burn all of his ezETH for ether.


 **Let's look at some values:**
Assuming the request is a full withdrawal, with an accumulated reward of 1 ether.

Initial State:
current ezETH supply = 32e18
current protocol TVL = 32 ether

Note that I am omitting other factors that could have influenced these values.

Assuming the attacker