For the complete documentation index, see llms.txt. This page is also available as Markdown.

57776 sc insight staking sol is not eip4626 compliant breaking integrations

Submitted on Oct 28th 2025 at 20:44:29 UTC by @Another for Audit Comp | Belong

  • Report ID: #57776

  • Report Type: Smart Contract

  • Report severity: Insight

  • Target: https://github.com/immunefi-team/audit-comp-belong/blob/main/contracts/v2/periphery/Staking.sol

  • Impacts:

    • Contract fails to deliver promised returns, but doesn't lose value

Description

Brief/Intro

The Staking contract fails to properly override ERC-4626's maxWithdraw and maxRedeem functions to consider the minStakePeriod parameter. This violates the ERC-4626 standard specification, which requires these functions to return the actual maximum amounts that can be withdrawn or redeemed without causing a revert.

Vulnerability Details

The current implementation inherits the base Solady ERC-4626 maxWithdraw and maxRedeem functions, which return the user's full balance without considering the staking lock period. However, the _withdraw function enforces _consumeUnlockedSharesOrRevert, which will revert if the minimum staking period hasn't been met. This creates a discrepancy where the maxWithdraw and maxRedeem functions indicate more is available than can actually be withdrawn.

Excerpt from the inherited implementations:

    /// @dev Returns the maximum amount of the underlying asset that can be withdrawn
    /// from the `owner`'s balance in the Vault, via a withdraw call.
    ///
    /// - MUST return a limited value if `owner` is subject to some withdrawal limit or timelock.
    /// - MUST NOT revert.
    function maxWithdraw(address owner) public view virtual returns (uint256 maxAssets) {
        maxAssets = convertToAssets(balanceOf(owner));
    }

    /// @dev Returns the maximum amount of Vault shares that can be redeemed
    /// from the `owner`'s balance in the Vault, via a redeem call.
    ///
    /// - MUST return a limited value if `owner` is subject to some withdrawal limit or timelock.
    /// - MUST return `balanceOf(owner)` otherwise.
    /// - MUST NOT revert.
    function maxRedeem(address owner) public view virtual returns (uint256 maxShares) {
        maxShares = balanceOf(owner);
    }

According to the ERC-4626 standard:

  • maxWithdraw: MUST factor in both global and user-specific limits, like if withdrawals are entirely disabled (even temporarily) it MUST return 0.

  • maxRedeem: MUST factor in both global and user-specific limits, like redemption being disabled; MUST NOT revert.

The contract's _withdraw calls _consumeUnlockedSharesOrRevert, which reverts if the minimum staking period isn't met, meaning calls to maxWithdraw / maxRedeem can return values that will cause a subsequent withdraw/redeem to revert.

Excerpt from _withdraw / stake bookkeeping:

Impact Details

ERC-4626 non-compliance leads to integration failures with other protocols and aggregators expecting ERC-4626 compliance; they may malfunction, breaking composability.

References

  • https://github.com/immunefi-team/audit-comp-belong/blob/a17f775dcc4c125704ce85d4e18b744daece65af/contracts/v2/periphery/Staking.sol#L248-L290

  • https://github.com/immunefi-team/audit-comp-belong/blob/a17f775dcc4c125704ce85d4e18b744daece65af/node_modules/solady/src/tokens/ERC4626.sol#L342-L360

  • https://eips.ethereum.org/EIPS/eip-4626

Proof of Concept

The following test demonstrates the discrepancy: maxWithdraw returns the full balance, but withdraw reverts due to minStakePeriod not being met.

Was this helpful?