Renzo - 14si2o_Flint'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: 44/122

Findings: 3

Award: $18.24

🌟 Selected for report: 0

🚀 Solo Findings: 0

Lines of code

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

Vulnerability details

Impact

The calculateTVLs() function in RestakeManager incorrectly calculates the TVL due to incorrect mixing of indexes in a nested loop. As a result, the amount to redeem calculated in the WithdrawQueue:: withdraw function will not be correct.

In the calculateTVLs()function, there is a nested loop with i for operatorDelegator and j for collateralToken. In the loop that counts the balance of the collateral tokens, the ERC20 token is index with i while the balance is indexed with j. This means that instead of looping through all the collateral tokens, the balance of each collateral token is counted as the balance of i collateral token.

Since the different collateral tokens are not equal in value and the different operatorDelegators will have different balances for each respective collateral token, the TVL calculated will be completely wrong.

Proof of Concept

Assume: 4 operator delegators and 3 collateral tokens odLength = [0,1,2,3] tokenLength = [token1,token2,token3]

In the below function we first have a for loop that cycles through the operators with the i as index. In this loop we find a nested loop which tries to add the value of all the collateral tokens to the tvl. It uses j as index.

Yet in this second loop both indexes are mixed. Which twists the calculations.

When i = 0, it tries to loop through the 3 collateral tokens to add the value to the TVL.

first loop: j = 0 : collateralTokens[0], collateralTokens[0].balanceOf() 2nd loop: j = 1 : collateralTokens[0], collateralTokens[1].balanceOf() 3rd loop: j = 2 : collateralTokens[0], collateralTokens[2].balanceOf()

So for the 1st operator, we get 0 for the 2nd & 3rd collateral token, while vastly overestimating the balance of the 1st collateral token. So for the 2nd operator, we get 0 for the 1st & 3rd collateral token, while vastly overestimating the balance of the 2nd collateral token. So for the 3rd operator, we get 0 for the 1st & 2nd collateral token, while vastly overestimating the balance of the 3rd collateral token.

In RestakeManager :: calculateTVLs()

                if (!withdrawQueueTokenBalanceRecorded) { 
                    totalWithdrawalQueueValue += renzoOracle.lookupTokenValue(
@>                    collateralTokens[i], 
                        collateralTokens[j].balanceOf(withdrawQueue)
                    );
                }

Since the different collateral tokens are not equal in value and the different operatorDelegators will have different balances for each respective collateral token, the TVL calculated will be completely wrong.

The impact is a definite loss of protocol users and/or the protocol, since the function is used to calculate the amount to redeem in the withdraw().

Tools Used

Manual Review

                if (!withdrawQueueTokenBalanceRecorded) { 
                    totalWithdrawalQueueValue += renzoOracle.lookupTokenValue(
-                        collateralTokens[i], 
+                        collateralTokens[j], 
                        collateralTokens[j].balanceOf(withdrawQueue)
                    );
                }

Assessed type

Loop

#0 - c4-judge

2024-05-16T10:31:45Z

alcueca marked the issue as satisfactory

#1 - c4-judge

2024-05-16T10:38:47Z

alcueca changed the severity to 2 (Med Risk)

#2 - c4-judge

2024-05-16T10:39:08Z

alcueca changed the severity to 3 (High Risk)

#3 - c4-judge

2024-05-20T04:26:26Z

alcueca changed the severity to 2 (Med Risk)

#4 - c4-judge

2024-05-23T13:47:21Z

alcueca changed the severity to 3 (High Risk)

Awards

0.0402 USDC - $0.04

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sufficient quality report
:robot:_20_group
duplicate-198

External Links

Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/RestakeManager.sol#L491-L576 https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Delegation/OperatorDelegator.sol#L140-L154

Vulnerability details

Impact

When a user calls RestakeManager :: deposit, the function first checks the if there is a buffer to be filled and subtracts the bufferToFill from the deposit amount.

If bufferToFill >= _amount, amount is set to 0 which means the operatorDelegator.deposit() will be called with 0 for _amount. This is a major problem since that function has a require that revert on zero deposit.

As a result, whenever a buffer needs to be filled, all deposits smaller than the buffer will revert.

Proof of Concept

Assume Alice deposits 5 eth in restakeManager. The buffer to fill is 6 eth. _amount = 5 eth bufferToFill = 6 eth

From the below code if statements we get: bufferToFill > 0 _amount <= bufferToFill ? Yes => bufferToFill = _amount _amount -= bufferTofill => setting _amount to 0

As a result the operatorDelegator.deposit(_collateralToken, _amount) function will be called with 0 for _amount.

        if (bufferToFill > 0) {
            bufferToFill = (_amount <= bufferToFill) ? _amount : bufferToFill;
            // update amount to send to the operator Delegator
            _amount -= bufferToFill;
            // safe Approve for depositQueue
            _collateralToken.safeApprove(address(depositQueue), bufferToFill);
            // fill Withdraw Buffer via depositQueue
            depositQueue.fillERC20withdrawBuffer(address(_collateralToken),  bufferToFill);
        }
        // Approve the tokens to the operator delegator
        _collateralToken.safeApprove(address(operatorDelegator), _amount);
        // @audit-info approve the reduced amount (can be 0)

        // Call deposit on the operator delegator
        operatorDelegator.deposit(_collateralToken, _amount);

In the deposit function we see a clear if statement that will cause a revert whenever tokenamount = 0.

    function deposit(
        IERC20 token,
        uint256 tokenAmount
    ) external nonReentrant onlyRestakeManager returns (uint256 shares) {
        if (address(tokenStrategyMapping[token]) == address(0x0) || tokenAmount == 0) 
            revert InvalidZeroInput();

        // Move the tokens into this contract
        token.safeTransferFrom(msg.sender, address(this), tokenAmount);

        return _deposit(token, tokenAmount);
    }

As a result, whenever the deposit is smaller than the buffer to be refilled, the deposit will revert and the user cannot interact with the protocol.

Tools Used

Manual Review

Either remove the 0 amount deposit require from the OperatorDelegator contract or change the buffer logic so that _amount will never be zero.

Assessed type

Other

#0 - c4-judge

2024-05-20T05:06:33Z

alcueca marked the issue as satisfactory

#1 - c4-judge

2024-05-24T10:26:23Z

alcueca changed the severity to 2 (Med Risk)

Findings Information

Awards

18.1958 USDC - $18.20

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sufficient quality report
:robot:_28_group
duplicate-103

External Links

Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/RestakeManager.sol#L244-L259 https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/RestakeManager.sol#L274-L358 https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Withdraw/WithdrawQueue.sol#L202-L263

Vulnerability details

Impact

The removeCollateralToken function breaks the calculateTVLs function since collateralTokens.length is used in the calculateTVLs function to determine the balance of the collateral tokens. If the removed collateral token is not the last token in the collateralTokens array before removal, the TVL calculation will use the removed collateral balance while not counting active collateral balance.

As a result, the TVL calculation will be wrong and the amount protocol users will be able to withdraw will be completely incorrect.

Proof of Concept

Assume: collateralTokens = [token1,token2,token3,token4] collateralTokens.length = 4 OperatorDelegator Alice with balances for each token. [100,10,27,66]

Admin calls removeCollateralToken for token3.
collateralTokens = [token1,token2,token4] collateralTokens.length = 3 OperatorDelegator Alice still have the same balances. [100,10,27,66]

The calculateTVLs function is used to to calculate the TVL based on collateralTokens.length. TVL = 0 collateralTokens.length = 3 Loop 1: TVL+= 100 Loop 2: TVL+= 10 Loop 3: TVL+= 27

TVL = 137 The balance of the removed collateral token3 is counted as valid TVL while the active token4 collateral token is not counted.

Tools Used

Manual Review

Before a collateral token is removed, all operatorDelegators must be required to completely empty their balance of said token. Only then should the function be called. This would require substantial additional logic to correctly implement this.

Assessed type

Loop

#0 - CloudEllie

2024-05-10T17:40:40Z

Adjusting duplicate grouping per validator @0xJuancito's recommendation

#1 - c4-judge

2024-05-17T13:53:37Z

alcueca marked the issue as not a duplicate

#2 - c4-judge

2024-05-17T13:53:47Z

alcueca changed the severity to 2 (Med Risk)

#3 - c4-judge

2024-05-17T13:54:13Z

alcueca changed the severity to 3 (High Risk)

#4 - c4-judge

2024-05-17T13:54:34Z

alcueca marked the issue as duplicate of #464

#5 - c4-judge

2024-05-17T14:04:10Z

alcueca marked the issue as duplicate of #97

#6 - c4-judge

2024-05-17T14:05:45Z

alcueca marked the issue as unsatisfactory: Invalid

#7 - c4-judge

2024-05-20T04:34:15Z

alcueca changed the severity to 2 (Med Risk)

#8 - c4-judge

2024-05-20T04:41:16Z

alcueca marked the issue as satisfactory

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