Incorrect implementation of max functions in ConcreteMultiStrategy
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
maxMintshould return the maximum mintable shares.The
maxDepositshould 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.