Renzo - Neon2835'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: 100/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
edited-by-warden
:robot:_20_group
duplicate-198

External Links

Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/RestakeManager.sol#L562 https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Delegation/OperatorDelegator.sol#L147-L148

Vulnerability details

Impact

When a user deposits ERC20 using the deposit function of the RestakeManager contract, the system checks whether the WithdrawQueue contract's withdrawal buffer needs to be filled:

uint256 bufferToFill = depositQueue.withdrawQueue().getBufferDeficit(
            address(_collateralToken)
        );

When the value of bufferToFill is greater than or equal to the user's deposit amount, the user's ERC20 deposit will be transferred to the WithdrawQueue contract, resulting in the deposit function of the OperatorDelegator contract being called with an input parameter _amount equal to 0. The deposit function of the OperatorDelegator contract is as follows:

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);
}

If the tokenAmount is equal to 0, the deposit function of the OperatorDelegator contract will revert, resulting in the rejection of the entire ERC20 deposit transaction.

Proof of Concept

Suppose the withdrawalBufferTarget value of stETH in the WithdrawQueue contract is equal to 100.

  1. A large investors user or institution withdraws 32 stETH normally, then the WithdrawQueue contract has a 32 stETH withdrawal buffer that needs to be filled, which means the getBufferDeficit(stETH) return value is equal to 32.

  2. At this time, any other user's deposit amount less than or equal to 32 stETH will be rejected.

  3. I found that most retail investors participating in Renzo protocol deposits have relatively small amounts(such as 0.1 stETH, 3 stETH, and other small amounts), so most retail investors may not be able to deposit. Only when new large investors or institutions participate in stETH deposits (or rewards from EigenLayer) and supplement the stETH in the WithdrawQueue contract, can retail investors continue to participate in the protocol deposit. However, during this period, deposits of less than or equal to 32 stETH will be rejected

The key to this vulnerability is that: If the bufferToFill value of an ERC20 is equal to X, then when the amount of staking is less than or equal to X, it will be rejected. Any large investor or institution making a normal withdrawal (or an attacker maliciously withdraw to manipulating the value of bufferToFill) will cause the value of X to become very large, which is very detrimental to retail investors.

Tools Used

Manual audit

Modify the deposit function of the OperatorDelegator contract. When tokenAmount=0, do not execute the revert statement, use the return 0; statement instead.

function deposit(
    IERC20 token,
    uint256 tokenAmount
) external nonReentrant onlyRestakeManager returns (uint256 shares) {
    if (address(tokenStrategyMapping[token]) == address(0x0) || tokenAmount == 0)
-        revert InvalidZeroInput();
+        return 0
    // Move the tokens into this contract
    token.safeTransferFrom(msg.sender, address(this), tokenAmount);

    return _deposit(token, tokenAmount);
}

Assessed type

Error

#0 - c4-judge

2024-05-20T05:02:17Z

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