-
Notifications
You must be signed in to change notification settings - Fork 4
Description
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
-
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 EulerEarn meta-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, or -
reallocate from the
VaultV2strategy into other strategies.
-
Observed behavior:
-
EulerEarncallsstrategy.maxWithdraw(address(this))and receives0. -
It then computes something equivalent to:
uint256 maxWithdrawFromStrategy = strategy.maxWithdraw(address(this));
uint256 toWithdraw = Math.min(maxWithdrawFromStrategy, assetsToDeallocate);-
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:
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
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
EulerEarn-
Reject strategies whose
maxWithdraw(address(this))is0whilebalanceOf(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(),EulerEarncould derive an upper bound from:
- Instead of relying solely on
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
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