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 V3arrow-up-right

  • 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 (0x6E745367F4Ad2b3da7339aee65dC85d416614D90arrow-up-right)

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:

  1. Comptroller Rejection:

    • Market has reached supply cap

    • Minter is blacklisted or paused

    • Comptroller is in emergency shutdown

  2. Market Not Fresh:

    • Block timestamp doesn't match accrual timestamp

    • Interest hasn't been accrued properly

  3. Interest Accrual Failure:

    • Interest rate model calculation fails

    • Accumulation causes overflow

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

Check return values and revert with clear error messages:

References

Proof of Concept

Proof of Concept

Proof of Concept Setup

1. Create Mock Contract (mock contract included below)

  • Path: test/mocks/MoonwellMock.sol

  • Purpose: Simulates Moonwell mToken behavior with controllable failure modes

2. Create Test File

  • Path: test/MoonwellSilentFailure.t.sol

  • Purpose: Demonstrates mint() and redeemUnderlying() return error codes instead of reverting

3. Run Tests

Mock contract (MoonwellMock.sol)

Was this helpful?