Assessment reports>Concrete>Informational findings>Incorrect implementation of ,max, functions in ConcreteMultiStrategy
Category: Coding Mistakes

Incorrect implementation of max functions in ConcreteMultiStrategy

Informational Impact
Informational Severity
N/A Likelihood

Description

ConcreteMultiStrategy implements the max function family incorrectly according to the ERC-4626 standard.

First, maxMint returns depositable assets instead of mintable shares:

function maxMint(address) public view override returns (uint256) {
    return (paused() || totalAssets() >= depositLimit) ? 0 : depositLimit - totalAssets();
}

Second, maxDeposit does not verify the paused status or deposit limit.

Third, maxWithdraw subtracts all pending fees instead of the user's proportional share:

function maxWithdraw(address owner) public view virtual override returns (uint256) {
    // Get the raw max withdrawal amount
    uint256 rawMaxWithdraw = _convertToAssets(balanceOf(owner), Math.Rounding.Floor);

    // Calculate pending fees
    uint256 pendingFees = accruedProtocolFee();

    // Return max withdraw minus pending fees
    return rawMaxWithdraw.mulDiv(MAX_BASIS_POINTS - fees.withdrawalFee, MAX_BASIS_POINTS, Math.Rounding.Floor)
        - pendingFees;
}

Fourth, maxWithdraw accounts for pending fees while maxRedeem does not. Neither function accounts for performance fees.

Fifth, maxWithdraw accounts for withdrawal fees while maxRedeem does not.

Impact

The max function implementations deviate from ERC-4626 specifications, potentially causing integration issues with protocols expecting standard behavior.

Recommendations

Implement all max functions according to ERC-4626 specifications:

  • The maxMint should return the maximum mintable shares.

  • The maxDeposit should verify paused status and deposit limits.

  • Fee calculations should apply proportionally to user shares.

  • All relevant fees should be consistently accounted for across related functions.

Remediation

This issue has been acknowledged by Blueprint Finance, and a partial fix was implemented in commit f5b133d9.

Blueprint Finance partially resolved this finding and provided the following response:

maxRedeem() returns the number of shares that can be redeemed without reverting, so it does not account for fees.

maxWithdraw() returns the maximum amount of the underlying asset that can be withdrawn from the owner’s balance, and does account for fees. This behavior is intentional and consistent with the ERC-4626 spec.

Zellic © 2025Back to top ↑