Renzo - MaslarovK'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: 101/122

Findings: 1

Award: $0.04

🌟 Selected for report: 0

🚀 Solo Findings: 0

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#L562

Vulnerability details

Impact

In the RestakeManager::depositfunction the contract checks the buffer deficit for the withdrawal queue first. Then it uses the deposited assets to cover the deficit as much as possible by filling the buffer, and if there are any token left it invests them. The problem is that if there are no tokens left it will revert since it will try to deposit zero.

That means that if there's a buffer deficit the next transaction needs to deposit enough funds to fill it fully in one single transaction, otherwise it will revert. Partial filling of the buffer is not allowed since there are no funds left and it reverts. Depending on the needed amount the deposited could be out of service for a long time till someone deposits enough funds.

Proof of Concept

    function test_deposit_ERC20_simple(uint256 amount) public {
        vm.assume(amount <= 1_000 ether);
        vm.prank(alice);
        // @audit if deposit amount <= buffer deficit 
        // in the first transacion buffer-deficit = buffer
        // this 
        if (amount <= buffer) {
            // InvalidZeroInput error in delegator 
            vm.expectRevert(abi.encodeWithSignature("InvalidZeroInput()"));
            restake.deposit(IERC20(ST_ETH_MAINNET), amount);
            return;
        }

        restake.deposit(IERC20(ST_ETH_MAINNET), amount);
    }

Tools Used

Manual review

Do nothing after spending the whole deposit amount in filling the buffer so the transaction doesn't revert:

    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;) {
                // esto sera 0
                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));

        //  fill buffer
        if (bufferToFill > 0) {
            // buffer to fill = min(amount, bufferToFill)
            bufferToFill = (_amount <= bufferToFill) ? _amount : bufferToFill;
            _amount -= bufferToFill;

            // safe Approve for depositQueue
            _collateralToken.safeApprove(address(depositQueue), bufferToFill);

            // fill Withdraw Buffer via depositQueue
            depositQueue.fillERC20withdrawBuffer(address(_collateralToken), bufferToFill);
        }

        // IF HERE AMOUNT IS NOT 0: 
        if(_amount !=0){
            // Approve the tokens to the operator delegator
            _collateralToken.safeApprove(address(operatorDelegator), _amount);

            // Call deposit on the operator delegator
            /// no revert
            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);
    }

Assessed type

Other

#0 - c4-judge

2024-05-20T05:01:36Z

alcueca marked the issue as satisfactory

#1 - c4-judge

2024-05-24T10:26:23Z

alcueca changed the severity to 2 (Med 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