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.
Observed behavior:
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:
-
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:
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
Summary
When
EulerEarnallocates funds to a MorphoVaultV2strategy, those funds cannot be withdrawn or reallocated.In this integration,
VaultV2returns0frommaxWithdraw(address)andEulerEarnusesmin(maxWithdraw, expectedSupplyAssets)when computing how much to withdraw. As a result,toWithdrawis always0, soEulerEarnnever actually callswithdraw()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
EulerEarn.sol– withdrawal / reallocation logic that usesmaxWithdraw()(around this line):https://github.com/euler-xyz/euler-earn/blob/master/src/EulerEarn.sol#L842
Morpho VaultV2implementation wheremaxWithdraw()is hard-coded to 0 (seeVaultV2.sol):https://github.com/morpho-org/vault-v2/blob/main/src/VaultV2.sol#L737-L740
ERC-4626 spec language for
maxWithdraw:https://eips.ethereum.org/EIPS/eip-4626#maxwithdraw
Details / reproduction
Deploy an
EulerEarnmeta-vault.Add a Morpho
VaultV2ERC-4626 vault as a strategy.Deposit assets into
EulerEarnand allow it to allocate to theVaultV2strategy.Attempt to:
withdraw from
EulerEarn, orreallocate from the
VaultV2strategy into other strategies.Observed behavior:
EulerEarncallsstrategy.maxWithdraw(address(this))and receives0.It then computes something equivalent to:
Since
maxWithdrawFromStrategy == 0,toWithdrawis always0.No underlying
withdraw()call is made; reallocation and user withdrawals from that strategy are effectively impossible.From the
EulerEarnpoint of view, the funds are permanently stuck in theVaultV2strategy.Standard-compliance angle
The EIP-4626 spec for maxWithdraw defines it as:
In this integration, there is underlying liquidity and a path to successfully exiting the position, but
maxWithdraw()always returns0. For an integrator likeEulerEarn, this is interpreted as “no withdrawable amount exists,” so the strategy never attempts awithdraw()and instead treats the position as non-withdrawable. Regardless of how the spec is interpreted, the combination of “real economic withdrawability” with “reportedmaxWithdraw() == 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:
VaultV2implementations (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
EulerEarnReject strategies whose
maxWithdraw(address(this))is0whilebalanceOf(address(this)) > 0, orAdd a one-time validation when adding a strategy to ensure
maxWithdraw()behaves sensibly.Alternative availability check
maxWithdraw(),EulerEarncould derive an upper bound from:and then use
min(available, assetsToDeallocate)as a withdrawal target, while still being prepared for partial withdrawals.Coordination with Morpho
VaultV2were to expose amaxWithdrawthat 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