58203 sc medium moonwell strategies silent failure due to unchecked mint and redeemunderlying return values
Submitted on Oct 31st 2025 at 10:52:38 UTC by @Brainiac5 for Audit Comp | Alchemix V3
Report ID: #58203
Report Type: Smart Contract
Report severity: Medium
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/strategies/optimism/MoonwellUSDCStrategy.sol
Impacts:
Permanent freezing of funds
Description
Summary
The Moonwell strategies (MoonwellUSDCStrategy, MoonwellWETHStrategy) use mint() and redeemUnderlying() functions that can return error codes instead of reverting on failure. The strategies don't check these return values, causing silent failures where the strategy believes operations succeeded when they actually failed.
function _allocate(uint256 amount) internal override returns (uint256) {
require(TokenUtils.safeBalanceOf(address(usdc), address(this)) >= amount, "Strategy balance is less than amount");
TokenUtils.safeApprove(address(usdc), address(mUSDC), amount);
// @audit No return value check - if mint fails, strategy thinks it succeeded
mUSDC.mint(amount);
return amount;
}
function _deallocate(uint256 amount) internal override returns (uint256) {
uint256 usdcBalanceBefore = TokenUtils.safeBalanceOf(address(usdc), address(this));
// @audit No return value check - if redeem fails, next require will revert
// but the error message won't indicate the real failure
mUSDC.redeemUnderlying(amount);
require(TokenUtils.safeBalanceOf(address(usdc), address(this)) >= amount, "Strategy balance is less than the amount needed");
TokenUtils.safeApprove(address(usdc), msg.sender, amount);
uint256 usdcBalanceAfter = TokenUtils.safeBalanceOf(address(usdc), address(this));
uint256 usdcRedeemed = usdcBalanceAfter - usdcBalanceBefore;
if (usdcRedeemed < amount) {
emit StrategyDeallocationLoss("Strategy deallocation loss.", amount, usdcRedeemed);
}
require(TokenUtils.safeBalanceOf(address(usdc), address(this)) >= amount, "Strategy balance is less than the amount needed");
return amount;
}Vulnerability Details
Moonwell mToken Mint Flow
Contract: Moonwell mUSDC (0x6E745367F4Ad2b3da7339aee65dC85d416614D90)
Step 1: External mint function returns error code
Step 2: Internal mint checks multiple conditions
Step 3: mintFresh performs critical validations
The Silent Failure Problem
What the strategy does:
What should happen if mint fails:
Failure Scenarios
Primary Issue: Mint Failure (Allocation)
When Moonwell mint() can fail and return error codes:
Comptroller Rejection:
Market has reached supply cap
Minter is blacklisted or paused
Comptroller is in emergency shutdown
Market Not Fresh:
Block timestamp doesn't match accrual timestamp
Interest hasn't been accrued properly
Interest Accrual Failure:
Interest rate model calculation fails
Accumulation causes overflow
Math Errors:
Exchange rate calculation fails
Token supply calculations overflow
Impact of ignored error:
Secondary Issue: Redeem Failure (Deallocation)
While deallocation failures will eventually cause a revert due to the balance check, the error message will be misleading:
Impact
Severity Justification
Silent Allocation Failures: Critical - funds never enter yield positions but accounting shows them as allocated and they are left stuck in the startegies.
Accounting Corruption: VaultV2 tracking diverges from reality, believing funds are deployed when they're idle
Loss of Yield: Capital sits unproductive in strategy contract instead of earning Moonwell supply APY
Recommended Fix
Check return values and revert with clear error messages:
References
Vulnerable Code:
Moonwell mUSDC Contract: 0x6E745367F4Ad2b3da7339aee65dC85d416614D90
Proof of Concept
Proof of Concept
Proof of Concept Setup
1. Create Mock Contract (mock contract included below)
Path:
test/mocks/MoonwellMock.solPurpose: Simulates Moonwell mToken behavior with controllable failure modes
2. Create Test File
Path:
test/MoonwellSilentFailure.t.solPurpose: Demonstrates mint() and redeemUnderlying() return error codes instead of reverting
3. Run Tests
Mock contract (MoonwellMock.sol)
Was this helpful?