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
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.