57311 sc medium moonwell allocation and deallocation can fail silently causing incorrect state updates and loss of yield

Submitted on Oct 25th 2025 at 06:29:13 UTC by @Oxdeadmanwalking for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #57311

  • Report Type: Smart Contract

  • Report severity: Medium

  • Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/strategies/optimism/MoonwellUSDCStrategy.sol

  • Impacts:

    • Smart contract unable to operate due to lack of token funds

    • Contract fails to deliver promised returns, but doesn't lose value

Description

Brief/Intro

Moonwell MYT strategies, namely MoonwellUSDCStrategy , MoonwellWETHStrategy on mainnet and OP, allocate funds to the underlying protocol by calling mToken.mint() and mToken.redeemUnderlying(). Those functions however can fail silently without reverting, but instead returning error codes which are not checked, causing the vault to think that it has allocated/deallocated assets from the strategy when in fact it has not. As a result, wrong realAssets are reported and no yield for the USDC that sits idle in the contract is generated.

Vulnerability Details

Allocation code for Moonwell strategies looks like the following. We will use the USDC strategy for reference here.

    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);
        // Mint mUSDC with underlying USDC
        // @audit, mUSDC mint can potentially revert. if this succeeds, it returns an error code of 0
        // but the code is not checked. if this fails, then the vault thinks it has allocated to the strategy
        // but in reality it has not
        mUSDC.mint(amount);
        return amount;
    }

Looking at the Moonwell contracts, if any operation (mint or redeem) fails for whatever reason, the transaction will not revert but instead return a non-zero error code: (https://github.com/moonwell-fi/contracts-open-source/blob/e23657c5fbeb12c7393fa49da6f350dc0bd5114e/contracts/core/MErc20.sol#L38-L47)

If allocation fails but the transaction succeeds, the morpho vault still increments assets allocated to this strategy, affecting its perception of caps, both relative and absolute. From VaultV2 (https://github.com/morpho-org/vault-v2/blob/406546763343b9ffa84c2f63742ae55a490b7c42/src/VaultV2.sol#L571C1-L591C6)

This makes the vault think that the underlying assets are generating yield according in line with the strategy risk but in reality the usdc will sit there idle and the protocol will incur an opportunity cost as the share price of MYT will not increase with the intended speed.

In addition, USDC will be sent to the adapter contract but realAssets() will in fact report less than the expected amount as there will me less mTokens in the contract:

Fortunately, idle USDC will still be withdrawable in deallocate since the function only checks the before and after balance of USDC underlying

When deallocating the problem presists since the before and after balance will not satisfy the amount requested, causing unexpected reverts.

Impact Details

Loss of expected yield since the vault will think that the funds are allocated when in fact they are not. realAssets will also report less TVL in the vault even though the funds have been sent but are not reflected as idle. If the Moonwell strategies represent a big proportion of the underlying assets in the vault, eventually this will also cause problems on redemptions of shares since there wont be available liquidity to withdraw assets from the vault (or the vault will think that there is not) , making users unable to realize profits from the yield bearing strategy. In addition, vault state also gets corrupted.

References

  • https://github.com/morpho-org/vault-v2/blob/main/src/VaultV2.sol

  • https://github.com/moonwell-fi/contracts-open-source/blob/e23657c5fbeb12c7393fa49da6f350dc0bd5114e/contracts/core/MErc20.sol#L38-L47

  • https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/strategies/optimism/MoonwellUSDCStrategy.sol

  • https://solodit.cyfrin.io/?b=false&f=&ff=&i=HIGH%2CMEDIUM&l=&maxf=&minf=&p=1&pc=&pn=&qs=1&r=true&rf=alltime&rs=1&s=moonwell&sd=Desc&sf=Recency&t=&u=&ur=true

Proof of Concept

Proof of Concept

Add these tests to MoonwellUSDCStrategy.t.sol:

  1. At the top of the file import:

  1. Run the tests

You should see that the vault incorrectly thinks that assets are allocated, without yield being generated:

Was this helpful?