xanoutas/solidity-security-findings huggingface dataset:
Calling `WithdrawQueue.pause` and `WithdrawQueue.unpause` functions are not effective
Audit: 2024-04-renzo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/549f774626b71dd13a427561d4743535a9cc0dca/contracts/Withdraw/WithdrawQueue.sol#L139-L141
https://github.com/code-423n4/2024-04-renzo/blob/549f774626b71dd13a427561d4743535a9cc0dca/contracts/Withdraw/WithdrawQueue.sol#L147-L149


# Vulnerability details

## Impact
The following `WithdrawQueue.pause` and `WithdrawQueue.unpause` functions are meant to pause and unpause functions of the `WithdrawQueue` contract. However, none of the `WithdrawQueue` contract's functions have the `whenNotPaused` modifier. As a result, calling these `WithdrawQueue.pause` and `WithdrawQueue.unpause` functions are not effective, and the `WithdrawQueue` contract's functions of interest that need to be paused during an emergency cannot be paused when needed.

https://github.com/code-423n4/2024-04-renzo/blob/549f774626b71dd13a427561d4743535a9cc0dca/contracts/Withdraw/WithdrawQueue.sol#L139-L141
```solidity
    function pause() external onlyWithdrawQueueAdmin {
        _pause();
    }
```


https://github.com/code-423n4/2024-04-renzo/blob/549f774626b71dd13a427561d4743535a9cc0dca/contracts/Withdraw/WithdrawQueue.sol#L147-L149
```solidity
    function unpause() external onlyWithdrawQueueAdmin {
        _unpause();
    }
```

## Proof of Concept
The following steps can occur for the described scenario.
1. A very volatile market condition occurs that causes a bank run.
2. The `WithdrawQueueAdmin` wants to temporarily pause the `WithdrawQueue.withdraw` and `WithdrawQueue.claim` functions until the market becomes more stable.
3. The `WithdrawQueueAdmin` calls the `WithdrawQueue.pause` function.
4. However, because such `WithdrawQueue.pause` function call is not effective, the `WithdrawQueue.withdraw` and `WithdrawQueue.claim` functions cannot be paused.
5. The bank run continues that negatively affects the protocol.

## Tools Used
Manual Review

## Recommended Mitigation Steps
The `whenNotPaused` modifier can be added to the `WithdrawQueue` contract's functions of interest.


## Assessed type

Other
Fixed hearbeat used for price validation is too stale for some tokens
Audit: 2024-04-renzo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Bridge/L2/Oracle/RenzoOracleL2.sol#L13
https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Bridge/L2/Oracle/RenzoOracleL2.sol#L52


# Vulnerability details

## Impact
The stale period 86400 + 60 seconds used for the oracle price validation is too short for some tokens like `ezETH` for example on Arbitrum. This could lead to the protocol consuming stale prices on Arbitrum.

## Proof of Concept
In both `RenzoOracle` and `RenzoOracleL2`, the hearbeat period `MAX_TIME_WINDOW` is set to `86400 + 60; // 24 hours + 60 seconds`. In the functions `RenzoOracleL2::getMintRate` and `RenzoOracle::lookupTokenValue`, a validation checks sees if the price data fed by Chainlink's price feed aggregators is stale depending if the period of 24 hours + 60 seconds has passed. Example :

```js
    function getMintRate() public view returns (uint256, uint256) {
        (, int256 price, , uint256 timestamp, ) = oracle.latestRoundData();
        if (timestamp < block.timestamp - MAX_TIME_WINDOW) revert OraclePriceExpired();
```

The problem is that depending on the token and the chain, the same period can be considered too small or too stale.

Let's consider the ezETH/ETH oracles on different chains:

- On Ethereum, the oracle will update the price data every [~24 hours](https://data.chain.link/feeds/ethereum/mainnet/ezeth-eth).
- On Arbitrum, the oracle will update the price data every [~6 hours](https://data.chain.link/feeds/arbitrum/mainnet/ezeth-eth-exchange-rate).
- On Linea, the oracle will update the price data every [24 hours](https://data.chain.link/feeds/linea/mainnet/ezeth-eth).
...

This means that on Arbitrum, 24 hours can be considered too large for the stale period which will cause the function `RenzoOracleL2::getMintRate` to return stale data.

## Tools Used

Manual review

## Recommended Mitigation Steps

It is recommanded to store a mapping that would record the hearbeat parameter for the stale period of each token and for every different chain.





## Assessed type

Oracle
Hardcoded heartbeat can cause the usage of stale prices
Audit: 2024-04-renzo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Oracle/RenzoOracle.sol#L71-L81
https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Oracle/RenzoOracle.sol#L85-L98


# Vulnerability details

## Impact
Hardcoding heartbeat to 24h + 60 seconds can lead to using very stale prices.

## Proof of Concept
Oracle price feeds can have different heartbeat intervals for different tokens and heartbeats can change over time.

[`RenzoOracle.sol:lookupTokenValue()`](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Oracle/RenzoOracle.sol#L71-L81) and [`RenzoOracle.sol:lookupTokenAmountFromValue()`](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Oracle/RenzoOracle.sol#L85-L98) use [`MAX_TIME_WINDOW`](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Oracle/RenzoOracle.sol#L26)

```solidity
/// @dev The maxmimum staleness allowed for a price feed from chainlink
uint256 constant MAX_TIME_WINDOW = 86400 + 60; // 24 hours + 60 seconds
```

```solidity
    function lookupTokenValue(IERC20 _token, uint256 _balance) public view returns (uint256) {
        AggregatorV3Interface oracle = tokenOracleLookup[_token];
        if (address(oracle) == address(0x0)) revert OracleNotFound();

        (, int256 price, , uint256 timestamp, ) = oracle.latestRoundData();
@>      if (timestamp < block.timestamp - MAX_TIME_WINDOW) revert OraclePriceExpired();
        if (price <= 0) revert InvalidOraclePrice();

        // Price is times 10**18 ensure value amount is scaled
        return (uint256(price) * _balance) / SCALE_FACTOR;
    }
```

If a token has a heartbeat of 1 hour then the price that we get from the oracle can be stale up to 23 hours which can cause significant economical damage to the protocol if the price becomes stale during a period of volatility

## Tools Used
Manual Review

## Recommended Mitigation Steps
Use the heartbeat of the specific oracle and have a way to update it if needed in the future.



## Assessed type

Oracle
possibility of DOS in functions that use calculateTVLs() due to scalability issues
Audit: 2024-04-renzo-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Functions that use [`RestakeManager:calculateTVLs()`](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/RestakeManager.sol#L274) can revert due to the increasing gas consumption with the scale of the protocol thus blocking main protocol functionalities.

## Proof of Concept
[`RestakeManager.sol:calculateTVLs()`](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/RestakeManager.sol#L274) is currently used in: 
1. [`RestakeManager.sol:depositETH()`](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/RestakeManager.sol#L594)
2. [`RestakeManager.sol:deposit()`](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/RestakeManager.sol#L473)
3. [`RestakeManager.sol:depositTokenRewardsFromProtocol()`](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/RestakeManager.sol#L652)
4. [`xRenzoBridge.sol:sendPrice()`](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Bridge/L1/xRenzoBridge.sol#L215) -> [`BalanceRateProvider.sol.getRate()`](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/RateProvider/BalancerRateProvider.sol#L31)
5. [`WithdrawQueue.sol:withdraw()`](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Withdraw/WithdrawQueue.sol#L206)

[`RestakeManager.sol:calculateTVLs()`](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/RestakeManager.sol#L274) loops for each operator through all accepted tokens.

Let's say we support 3 tokens and 5 operators = 15 iterations

On each iteration we make 1 call to EigenLayer + 1 call to an oracle. 

On the first operator iteration we make additional calls for each token to see the tvl in the withdrawalQueue contract.

so far we have:
withdrawal queue value = 3 tokens * 1 call = 3 oracle calls
3 tokens * 5 operators = 15 iterations * (1 oracle call + 1 EigenLayer call) = 30 calls
In total 33 calls.

If we double the operators count and the tokens count the total calls will become:
withdrawal queue value = 6 tokens * 1 call = 6 oracle calls
6 tokens * 10 operators = 60 iterations * (1 oracle call + 1 EigenLayer call) = 120 calls
In total 126 calls.

With scale the gas consumption increases times 4.

At this rate [`RestakeManager:calculateTVLs()`](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/RestakeManager.sol#L274) will become very expensive to call and can cause DOS due to reaching the block gas limit.

## Tools Used
Manual Review

## Recommended Mitiga
Reward amount is miscalculated in `DepositQueue::receive()` function.
Audit: 2024-04-renzo-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Title
Reward amount is miscalculated in `DepositQueue::receive()` function.

## Line of code
https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Deposits/DepositQueue.sol#L163C3-L174C58

## Impact
The [DepositQueue::receive()]() is used to receive ETH rewards from scenarios like Execution Layer reward and MEV from native staking. Here if the `feeAddress != address(0x0) && feeBasisPoints > 0` then the fee amount calculated is being sent to the `feeAddress` and then after the check it calculates the `uint256 remainingRewards = msg.value - feeAmount;` to fill the withdrawal buffer. 

Now we record the `totalEarned[address(0x0)]` like this `totalEarned[address(0x0)] = totalEarned[address(0x0)] + remainingRewards;` 

Now the problem is that, `remainingRewards` is already sent to fill the buffer.

This is again recorded in `totalEarned[address(0x0)]` which will inflate the totalEarned with a false `remainingRewards`.

Consider the case when remainingRewards is 0.8 ETH, and when filling 0.8 withdrawal buffer, the 0.8 ETH is sent to the withdrawal queue already,

yet this 0.8 ETH is count as the reward again.

```solidity
    receive() external payable nonReentrant {
        uint256 feeAmount = 0;
        // Take protocol cut of rewards if enabled
        if (feeAddress != address(0x0) && feeBasisPoints > 0) {
@>            feeAmount = (msg.value * feeBasisPoints) / 10000;
@>          (bool success, ) = feeAddress.call{ value: feeAmount }("");

            if (!success) revert TransferFailed();

            emit ProtocolFeesPaid(IERC20(address(0x0)), feeAmount, feeAddress);
        }
        // update remaining rewards
@>       uint256 remainingRewards = msg.value - feeAmount;
        // Check and fill ETH withdraw buffer if required
@>      _checkAndFillETHWithdrawBuffer(remainingRewards);

        // Add to the total earned
@>       totalEarned[address(0x0)] = totalEarned[address(0x0)] + remainingRewards;

        // Emit the rewards event
        emit RewardsDeposited(IERC20(address(0x0)), remainingRewards);
    }

```
The inflate reward amount will impact on [getTotalRewardsEarned()]() function. This function is used to calculate the total amount of rewards earned by the protocol. Rewards include staking native ETH and EigenLayer rewards (ETH + ERC20s). Here `totalRewards += depositQueue.totalEarned(address(0x0));` 
includes the `totalEarned(address(0x0))` which contains the false `remainingRewards` which was already sent to fill buffer leading to miscalculation of Total Reward Earned.

```solidity
    function getTotalRewardsEarned() external view returns (uint256) {
        uint256 totalRewards = 0;

        // First get the ETH rewards tracked in the deposit queue
@>       totalRewards += depositQueue.totalEarned(address(0x0)
WETH transfer compatibility with different chains
Audit: 2024-04-renzo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Bridge/L2/xRenzoDeposit.sol#L214
https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Bridge/L2/xRenzoDeposit.sol#L68


# Vulnerability details

## Impact
In xRenzoDEposit.sol users can deposit both NATIVEETH in `depositETH` which is wrapped into WETH  or directly deposit WETH as the collateral token in `deposit` and mints xezETH.
The problem lies in the 2nd deposit function because of WETH compatibility in different chains.

## Proof of Concept
```
   function deposit(
        uint256 _amountIn,
        uint256 _minOut,
        uint256 _deadline
    ) external nonReentrant returns (uint256) {
        if (_amountIn == 0) {
            revert InvalidZeroInput();
        }

        // Transfer deposit tokens from user to this contract
        depositToken.safeTransferFrom(msg.sender, address(this), _amountIn);

        return _deposit(_amountIn, _minOut, _deadline);
    }
```
However, the transfer done using `weth.safeTransferFrom`  This works fine on most chains (Ethereum, Optimism, Polygon, BSC) which uses the standard WETH9 contract that handles the case  when src == msg.sender:
But for chains like Fantom/Arbitrum/Blast it has a different implementation

## Tools Used
Manual Review

## Recommended Mitigation Steps
Use safeTransfer instead of safeTransferFrom


## Assessed type

ERC20
Failure of Deposit Function Due to WETH Allowance Handling on Certain Chains
Audit: 2024-04-renzo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/1c7cc4e632564349b204b4b5e5f494c9b0bc631d/contracts/Bridge/L2/xRenzoDeposit.sol#L214


# Vulnerability details

## Impact
The `deposit` function may fail on chains with non-standard `WETH` implementations lacking self-allowance handling.

## Proof of Concept
The `deposit` function in the `xRenzoDeposit` contract, which is intended to transfer deposit tokens from the user to the contract itself.
```javascript
    function deposit(
        uint256 _amountIn,
        uint256 _minOut,
        uint256 _deadline
    ) external nonReentrant returns (uint256) {
        if (_amountIn == 0) {
            revert InvalidZeroInput();
        }

        //@audit Transfer deposit tokens from user to this contract
        // weth in arbitrium will not work and user will not deposit
        depositToken.safeTransferFrom(msg.sender, address(this), _amountIn);

        return _deposit(_amountIn, _minOut, _deadline);
    }
```
However, the transfer done using `depositToken.safeTransferFrom`. This works fine on most chains (Ethereum, Optimism, Polygon, BSC) which uses the standard WETH9 contract that handles the case  when src == msg.sender:
```javascript
WETH9.sol
        if (src != msg.sender && allowance[src][msg.sender] != uint(- 1)) {
            require(allowance[src][msg.sender] >= wad);
            allowance[src][msg.sender] -= wad;
        }
```
The problem is that the `WETH` implementation on [Arbitrum](https://arbiscan.io/address/0x8b194beae1d3e0788a1a35173978001acdfba668#code) , Blast, uses a different contract, and does not have this  `src == msg.sender handling`.

Also, the issue is presented in Wrapped Arbitrum and  Wrapped Fantom.

As a result, any attempt to execute this line of code with WETH as the deposit token on `Arbitrumc`, Blast will fail with an "ERC20: transfer amount exceeds allowance" error, effectively breaking the deposit functionality when users attempt to deposit ETH.

[Similar finding](https://github.com/code-423n4/2023-05-chainlink-findings/issues/593).

## Tools Used
Manual Review

## Recommended Mitigation Steps
```diff
 function deposit(
        uint256 _amountIn,
        uint256 _minOut,
        uint256 _deadline
    ) external nonReentrant returns (uint256) {
        if (_amountIn == 0) {
            revert InvalidZeroInput();
        }

--        depositToken.safeTransferFrom(msg.sender, address(this), _amountIn);

++         depositToken.safeTransfer(msg.sender, address(this), _amountIn);

        return _deposit(_amountIn, _minOut, _deadline);
    }
```


## Assessed type

Error
Incorrect allocation check when choosing operator delegators
Audit: 2024-04-renzo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/main/contracts/RestakeManager.sol#L378-L381


# Vulnerability details

## Impact

`chooseOperatorDelegatorForDeposit()` is called whenever a deposit is initiated to choose the operator based on percentage alllocatons allocated by admin for eacho operators.

There is a check that checks each operator delegator TVL against their allocation by admin, if it is still lower, deposits will go through into that operator delegator. 


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

```solidity
function chooseOperatorDelegatorForDeposit(
    uint256[] memory tvls,
    uint256 totalTVL
) public view returns (IOperatorDelegator) {
    // Ensure OperatorDelegator list is not empty
    if (operatorDelegators.length == 0) revert NotFound();

    // If there is only one operator delegator, return it
    if (operatorDelegators.length == 1) {
        return operatorDelegators[0];
    }

    // Otherwise, find the operator delegator with TVL below the threshold
    // @audit wrong additional division of basis points, won't revert, can bypass TVL limits
    uint256 tvlLength = tvls.length;
    for (uint256 i = 0; i < tvlLength; ) {
        if (
@>           tvls[i] <
            (operatorDelegatorAllocations[operatorDelegators[i]] * totalTVL) /
                BASIS_POINTS /
                BASIS_POINTS
        ) {
            return operatorDelegators[i];
        }

        unchecked {
            ++i;
        }
    }

    // Default to the first operator delegator
    return operatorDelegators[0];
}
```

Notice the computation of `operatorDelegatorAllocations * totalTVL / BASIS_POINTS / BASIS_POINTS`. Since  `operatorDelegatorAllocations` is determined in 2 decimal precision, we shouldn't be dividing by `BASIS_POINTS` again, and as such this incorrect additional division will always overestimate the intended allocation allowed for each operator by 100x, causing the function to return early and deposit going through to the operator delegator even if tvl limit had been reached.

## Tools Used
Manual Analysis

## Recommended Mitigation Steps
```diff
function chooseOperatorDelegatorForDeposit(
    uint256[] memory tvls,
    uint256 totalTVL
) public view returns (IOperatorDelegator) {
    // Ensure OperatorDelegator list is not empty
    if (operatorDelegators.length == 0) revert NotFound();

    // If there is only one operator delegator, return it
    if (operatorDelegators.length == 1) {
        return operatorDelegators[0];
    }

    // Otherwise, find the operator delegator with TVL below the threshold
    uint256 tvlLength = tvls.length;
    for (uint256 i = 0; i < tvlLength; ) {
        if (
            tvls[i] <
            (operatorDelegatorAllocations[operatorDelegators[i]] * totalTVL) /
+               BASIS_POINTS            
-               BASIS_POINTS /
-               BASIS_POINTS
        ) {
            retur
Lack of Enforcement of Total Allocation Basis Point Maximum Will cause Incorrect Operator Delegation allocations
Audit: 2024-04-renzo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/RestakeManager.sol#L187-L212
https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/RestakeManager.sol#L131-L157
https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/RestakeManager.sol#L362-L389


# Vulnerability details

## Impact
When the protocol issues allocation basis point to different operator delegators, there is a lack of check that the total allocated basis point for all operator delegators involved is not > 10000/100%. This lack of verification will allow the same piece of the Total TVL to be allocated to the different Operator Delegators among other impacts like 

- Process of Distribution of the total TVL will be impended, as some Operator Delegators, will have their share of the total TVL allocated to another Operator Delegator.
- Incorrect TVL Threshold Calculation
- An Operator Delegator that should not be Considered as below the threshold will be incorrectly assumed to be below the threshold.

## Proof of Concept
The issue here is fairly simple to decipher, in a normal calculations, where tokens or deposits are sent to contract and the amount of tokens that each contract will receive is based upon basis points/% the total allocated should not be greater than 10000bps/100%, in any case should this happen then the amount of tokens that will be shared will not be enough for all receiving contract, But in the context of the `RestakeManager` of the Renzo protocol, the impact is more than that as it affects the manner in which Operator Delegators are chosen for deposits and withdrawals.

They are two functions where this check could be enforced but they were not as shown below 

```solidity
 function addOperatorDelegator(
        IOperatorDelegator _newOperatorDelegator,
        uint256 _allocationBasisPoints
    ) external onlyRestakeManagerAdmin {
..more code
        // Verify a valid allocation
        if (_allocationBasisPoints > (100 * BASIS_POINTS)) revert OverMaxBasisPoints();
...more code 
```
In this Function to add new ODs, there is a check to make sure that the current basis point that is about to be allocated is not greater than 100%, but not that the total basis point allocated to each OD is not greater than 100%.

The other function that handles ODs allocation basis point also did not implement this check 

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

```solidity
function setOperatorDelegatorAllocation(
        IOperatorDelegator _operatorDelegator,
        uint256 _allocationBasisPoints
    ) external onlyRestakeManagerAdmin {
        if (address(_operatorDelegator) == address(0x0)) revert InvalidZeroInput();
        if (_allocationBasisPoints > (100 * BASIS_POINTS)) revert OverMaxBasisPoints();

        // Ensure the OD is in t