Renzo - 0xnev'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: 109/122

Findings: 1

Award: $0.00

🌟 Selected for report: 0

🚀 Solo Findings: 0

Lines of code

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

Vulnerability details

Impact

In RestakeManager.calculateTVLs(), it computes the total value interms of ETH within the protocol.

It consists of the following additions:

  1. operatorTVL - Token value in terms of ETH of Operator balances for each collateral
  2. totalWithdrawalQueueValue - Token value of collateral in terms of ETH in queued withdrawals
  3. operatorETHBalance - Operator delegator staked ETH balance
  4. address(depositQueue).balance - Total ETH in deposit queue
  5. address(withdrawQueue).balance - Total ETH in withdrawal queue

For token value of collateral in queued withdrawal queue, the value is computed and retrieved as per the following:

bool withdrawQueueTokenBalanceRecorded = false;
address withdrawQueue = address(depositQueue.withdrawQueue());

// withdrawalQueue total value
// 4. Initialize variable to compute amount in withdrawal queu
uint256 totalWithdrawalQueueValue = 0;

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

    // Track the individual token TVLs for this OD - native ETH will be last item in the array
    uint256[] memory operatorValues = new uint256[](collateralTokens.length + 1);
    operatorDelegatorTokenTVLs[i] = operatorValues;

    // Iterate through the tokens and get the value of each
    uint256 tokenLength = collateralTokens.length;
    for (uint256 j = 0; j < tokenLength; ) {
        // Get the value of this token
        uint256 operatorBalance = operatorDelegators[i].getTokenBalanceFromStrategy(
            collateralTokens[j]
        );

        // Set the value in the array for this OD
        operatorValues[j] = renzoOracle.lookupTokenValue(
            collateralTokens[j],
            operatorBalance
        );

        // Add it to the total TVL for this OD
        operatorTVL += operatorValues[j];

        // record token value of withdraw queue
        if (!withdrawQueueTokenBalanceRecorded) {
            totalWithdrawalQueueValue += renzoOracle.lookupTokenValue(
                // @audit wrong index used
@>              collateralTokens[i],
                collateralTokens[j].balanceOf(withdrawQueue)
            );
        }

        unchecked {
            ++j;
        }
    }

Notice how the withdrawQueueTokenBalanceRecorded is only set once to false before the first loop starts, but every collateral token value for queued withdrawals per operator delegator looks up token value using the first collateraltoken collateral (i.e. collateralTokens[0]). This would mean a completely different value would be computed (higher/lower depending on which is the first collateral added) for totalWithdrawalQueueValue across all operator delegators, which inturn leads to a inaccurate (higher/lower) TVL computed.

This ultimately impacts all deposit functions which essentially causes the following impact

  1. Mint a higher/lower amount of ezETH than intended for users due to inaccurate computations within renzoOracle.calculateMintAmount()
  2. Allow bypass of maxDepositTVL set

Tools Used

Manual Analysis

bool withdrawQueueTokenBalanceRecorded = false;
address withdrawQueue = address(depositQueue.withdrawQueue());

// withdrawalQueue total value
// 4. Initialize variable to compute amount in withdrawal queu
uint256 totalWithdrawalQueueValue = 0;

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

    // Track the individual token TVLs for this OD - native ETH will be last item in the array
    uint256[] memory operatorValues = new uint256[](collateralTokens.length + 1);
    operatorDelegatorTokenTVLs[i] = operatorValues;

    // Iterate through the tokens and get the value of each
    uint256 tokenLength = collateralTokens.length;
    for (uint256 j = 0; j < tokenLength; ) {
        // Get the value of this token
        uint256 operatorBalance = operatorDelegators[i].getTokenBalanceFromStrategy(
            collateralTokens[j]
        );

        // Set the value in the array for this OD
        operatorValues[j] = renzoOracle.lookupTokenValue(
            collateralTokens[j],
            operatorBalance
        );

        // Add it to the total TVL for this OD
        operatorTVL += operatorValues[j];

        // record token value of withdraw queue
        if (!withdrawQueueTokenBalanceRecorded) {
            totalWithdrawalQueueValue += renzoOracle.lookupTokenValue(
-               collateralTokens[i],
+               collateralTokens[j],
                collateralTokens[j].balanceOf(withdrawQueue)
            );
        }

        unchecked {
            ++j;
        }
    }

Assessed type

Other

#0 - c4-judge

2024-05-16T10:38:04Z

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)

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