Skip to content

Funds allocated to Morpho VaultV2 are not withdrawable because maxWithdraw() is hard-coded to 0 #41

@brianmcmichael

Description

@brianmcmichael

Summary

When EulerEarn allocates funds to a Morpho VaultV2 strategy, those funds cannot be withdrawn or reallocated.

In this integration, VaultV2 returns 0 from maxWithdraw(address) and EulerEarn uses min(maxWithdraw, expectedSupplyAssets) when computing how much to withdraw. As a result, toWithdraw is always 0, so EulerEarn never actually calls withdraw() on the strategy and the position is effectively stuck. This issue describes the behavior from the EulerEarn side; the Morpho team has already been made aware separately.

Affected code

Details / reproduction

Deploy an EulerEarn meta-vault.

  • Add a Morpho VaultV2 ERC-4626 vault as a strategy.

  • Deposit assets into EulerEarn and allow it to allocate to the VaultV2 strategy.

  • Attempt to:

    • withdraw from EulerEarn, or

    • reallocate from the VaultV2 strategy into other strategies.

Observed behavior:

  • EulerEarn calls strategy.maxWithdraw(address(this)) and receives 0.

  • It then computes something equivalent to:

uint256 maxWithdrawFromStrategy = strategy.maxWithdraw(address(this));
uint256 toWithdraw = Math.min(maxWithdrawFromStrategy, assetsToDeallocate);
  • Since maxWithdrawFromStrategy == 0, toWithdraw is always 0.

  • No underlying withdraw() call is made; reallocation and user withdrawals from that strategy are effectively impossible.

  • From the EulerEarn point of view, the funds are permanently stuck in the VaultV2 strategy.

Standard-compliance angle

The EIP-4626 spec for maxWithdraw defines it as:

Maximum amount of the underlying asset that can be withdrawn from the owner balance in the Vault through a withdraw call. It MUST return the maximum amount that could be transferred without revert (it may underestimate), MUST factor in global and user-specific limits, MUST return 0 if withdrawals are entirely disabled, and MUST NOT revert.

In this integration, there is underlying liquidity and a path to successfully exiting the position, but maxWithdraw() always returns 0. For an integrator like EulerEarn, this is interpreted as “no withdrawable amount exists,” so the strategy never attempts a withdraw() and instead treats the position as non-withdrawable. Regardless of how the spec is interpreted, the combination of “real economic withdrawability” with “reported maxWithdraw() == 0” creates an assumption mismatch that results in stuck funds for this integration.

Impact

  • Any EulerEarn meta-vault that allocates to Morpho VaultV2 ends up with a portion of its assets that:

    • cannot be withdrawn by depositors, and

    • cannot be rebalanced/reallocated into other strategies.

  • From a user perspective, those assets are effectively unrecoverable unless the contracts are upgraded or some non-standard intervention is performed.

Suggested remediation

At minimum:

  • One immediate mitigation on the EulerEarn side would be to avoid allocating to Morpho’s VaultV2 implementations (or any ERC-4626 where maxWithdraw() can be permanently 0 despite a positive balance), and to document this clearly so integrators don’t add such strategies unintentionally.

Possible longer-term options:

  • Validation / guard rails in EulerEarn

    • Reject strategies whose maxWithdraw(address(this)) is 0 while balanceOf(address(this)) > 0, or

    • Add a one-time validation when adding a strategy to ensure maxWithdraw() behaves sensibly.

  • Alternative availability check

    • Instead of relying solely on maxWithdraw(), EulerEarn could derive an upper bound from:
uint256 available = strategy.convertToAssets(strategy.balanceOf(address(this)));

and then use min(available, assetsToDeallocate) as a withdrawal target, while still being prepared for partial withdrawals.

  • This is more robust against non-standard behavior but obviously changes assumptions around liquidity constraints.

Coordination with Morpho

  • If VaultV2 were to expose a maxWithdraw that returns a non-zero value when withdrawals are operationally feasible, then the existing EulerEarn logic should start working without changes for this strategy.

Given the current code, an immediate mitigation on the Euler side would be to avoid using Morpho VaultV2 as a strategy, as this combination currently results in zero withdrawable amounts being reported in EulerEarn.

Relevant URLs

EulerEarn code:
https://github.com/euler-xyz/euler-earn/blob/master/src/EulerEarn.sol

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

ERC-4626 standard (maxWithdraw):
https://eips.ethereum.org/EIPS/eip-4626#maxwithdraw

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions