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

2 stars 2 forks source link

LST Token Deposit Mechanism Enables Manipulative Accumulation of ezETH Shares #680

Closed c4-bot-9 closed 5 months ago

c4-bot-9 commented 5 months ago

Lines of code

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

Vulnerability details

Impact

The vulnerability allows an adversary to exploit the system by depositing LST tokens into the RestakeManager, causing an immediate increase in the price of ezETH ("shares") in ETH terms. By leveraging this manipulation, the attacker can swap ezETH tokens for other LST tokens on decentralized exchanges (DEXs) and subsequently deposit those LST tokens back into the pool. Through iterative execution of this process, the adversary can potentially amass a majority of the ezETH tokens without genuinely restaking or providing liquidity to the pool.

Proof of Concept

The attacker initiates the exploit by depositing LST tokens into the RestakeManager contract, resulting in an immediate rise in the price of ezETH shares in terms of ETH. Subsequently, the attacker swaps ezETH tokens for other LST tokens on DEXs and deposits these LST tokens back into the pool. This sequence allows the adversary to acquire a majority of the ezETH shares without actually engaging in legitimate restaking activities.

    function deposit(
        IERC20 _collateralToken,
        uint256 _amount,
        uint256 _referralId
    ) public nonReentrant notPaused {
        // Verify collateral token is in the list - call will revert if not found
        uint256 tokenIndex = getCollateralTokenIndex(_collateralToken);

        // Get the TVLs for each operator delegator and the total TVL
        (
            uint256[][] memory operatorDelegatorTokenTVLs,
            uint256[] memory operatorDelegatorTVLs,
            uint256 totalTVL
        ) = calculateTVLs();

        // Get the value of the collateral token being deposited
        uint256 collateralTokenValue = renzoOracle.lookupTokenValue(_collateralToken, _amount);

        // Enforce TVL limit if set, 0 means the check is not enabled
        if (maxDepositTVL != 0 && totalTVL + collateralTokenValue > maxDepositTVL) {
            revert MaxTVLReached();
        }

        // Enforce individual token TVL limit if set, 0 means the check is not enabled
        if (collateralTokenTvlLimits[_collateralToken] != 0) {
            // Track the current token's TVL
            uint256 currentTokenTVL = 0;

            // For each OD, add up the token TVLs
            uint256 odLength = operatorDelegatorTokenTVLs.length;
            for (uint256 i = 0; i < odLength; ) {
                currentTokenTVL += operatorDelegatorTokenTVLs[i][tokenIndex];
                unchecked {
                    ++i;
                }
            }

            // Check if it is over the limit
            if (currentTokenTVL + collateralTokenValue > collateralTokenTvlLimits[_collateralToken])
                revert MaxTokenTVLReached();
        }

        // Determine which operator delegator to use
        IOperatorDelegator operatorDelegator = chooseOperatorDelegatorForDeposit(
            operatorDelegatorTVLs,
            totalTVL
        );

        // Transfer the collateral token to this address
        _collateralToken.safeTransferFrom(msg.sender, address(this), _amount);

        // Check the withdraw buffer and fill if below buffer target
        uint256 bufferToFill = depositQueue.withdrawQueue().getBufferDeficit(
            address(_collateralToken)
        );
        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);

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

        // Calculate how much ezETH to mint
        uint256 ezETHToMint = renzoOracle.calculateMintAmount(
            totalTVL,
            collateralTokenValue,
            ezETH.totalSupply()
        );

        // Mint the ezETH
        ezETH.mint(msg.sender, ezETHToMint);

        // Emit the deposit event
        emit Deposit(msg.sender, _collateralToken, _amount, ezETHToMint, _referralId);
    }        

Tools Used

Manual Review

Recommended Mitigation Steps

Introduce a time-lock mechanism that prevents newly minted ezETH tokens from being swapped on a decentralized exchange (DEX) for a certain period. This delay would prevent an attacker from immediately swapping the tokens and repeating the process to accumulate a majority of the shares.

Assessed type

Other

DadeKuma commented 5 months ago

Insufficient proof

DadeKuma commented 5 months ago

@howlbot reject