58093 sc medium morpho reward in morphoyearnogweth will be lost or stuck

Submitted on Oct 30th 2025 at 15:33:32 UTC by @oxrex for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #58093

  • Report Type: Smart Contract

  • Report severity: Medium

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

  • Impacts:

    • Permanent freezing of unclaimed yield

Description

Brief/Intro

For the MorphoYearnOGWETH vault, MORPHO portion of the earned yield will be lost since some percentage of the total APY is paid out in MOR tokens.

Vulnerability Details

// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {MYTStrategy} from "../../MYTStrategy.sol";
import {IMYTStrategy} from "../../interfaces/IMYTStrategy.sol";
import {TokenUtils} from "../../libraries/TokenUtils.sol";

...

/**
 * @title MorphoYearnOGWETHStrategy
 * @notice This strategy is used to allocate and deallocate weth to the Morpho Yearn OG WETH vault on Mainnet
 */
contract MorphoYearnOGWETHStrategy is MYTStrategy {
    WETH public immutable weth;
    IERC4626 public immutable vault;

    event MorphoYearnOGWETHStrategyDebugLog(string message, uint256 value);

    constructor(address _myt, StrategyParams memory _params, address _vault, address _weth, address _permit2Address)
        MYTStrategy(_myt, _params, _permit2Address, _weth)
    {
        weth = WETH(_weth);
        vault = IERC4626(_vault);
        require(vault.asset() == _weth, "Vault asset != WETH");
    }

    // @note seen
    function _allocate(uint256 amount) internal override returns (uint256) {
        require(TokenUtils.safeBalanceOf(address(weth), address(this)) >= amount, "Strategy balance is less than amount");
        TokenUtils.safeApprove(address(weth), address(vault), amount);
        vault.deposit(amount, address(this));
        return amount;
    }

    function _deallocate(uint256 amount) internal override returns (uint256) {
        vault.withdraw(amount, address(this), address(this));
        uint256 wethBalanceBefore = TokenUtils.safeBalanceOf(address(weth), address(this));
        uint256 wethBalanceAfter = TokenUtils.safeBalanceOf(address(weth), address(this));
        uint256 wethRedeemed = wethBalanceAfter - wethBalanceBefore;
        if (wethRedeemed < amount) {
            emit StrategyDeallocationLoss("Strategy deallocation loss.", amount, wethRedeemed);
        }
        require(wethRedeemed + wethBalanceBefore >= amount, "Strategy balance is less than the amount needed");
        require(TokenUtils.safeBalanceOf(address(weth), address(this)) >= amount, "Strategy balance is less than the amount needed");
        TokenUtils.safeApprove(address(weth), msg.sender, amount);
        return amount;
    }

    ...
}

The MorphoYearnOGWETH contract we interact with on Ethereum mainnet has an APY of 3.97% per year (3.8% Native APY, 0.17% MORPHO APY). In that, there is a 0.19% performance fee charged to users. Now, the net APY wil be 3.79% i.e Morpho will take 0.19% from the Native Yield of 3.8% which means the Native APY will be 3.61% and then Morpho APY is 0.17% which results in the total APY of ~3.79%

Now, Morpho suppliers in this Yearn OG WETH 0xE89371eAaAC6D46d4C3ED23453241987916224FC vault will get two rewards.

  1. Reward 1 will be distributed in WETH tokens

  2. Reward 2 will be distributed in MORPHO tokens

You can check the breakdown here at the Vault page in the Morpho App: https://app.morpho.org/ethereum/vault/0xE89371eAaAC6D46d4C3ED23453241987916224FC/yearn-og-weth

The problem is that in the MorphoYearnOGWETHStrategy Alchemix is deploying, there is nowhere they setup:

  • The MORPHO URD CONTRACT which resides here onchain for MORPHO tokens: 0x330eefa8a787552DC5cAd3C3cA644844B1E61Ddb

Thus, MORPHO URD contract's claim() function will not be possible to be called from the contract. That is only one part of the issue.

The second part of the issue is that, these MORPHO tokens are sent to the account which in this case will be the MorphoYearnOGWETHStrategy address as that will be the address computed in the merkle reward root for the URD contract by Morpho protocol.

As a result of this, even if the Alchemix team were to obtain a valid proof and then call the URD's claim() function from another address (which is possible by the way to claim on behalf of someone), these tokens are in fact sent to the account which in this case is the MorphoYearnOGWETHStrategy address.

Thus, they will be locked inside the contract and cannot be claimed.

You can check the claim function of the URD here: https://etherscan.io/address/0x330eefa8a787552dc5cad3c3ca644844b1e61ddb#code#F1#L136

Impact Details

A part of the yield earned (MORPHO) will be stuck inside the strategy contract and cannot be retrived. From my forked test case of allocating 1k WETH to the Morpho Yearn OG vault on Morpho, the relative amount stuck will be 0.17% of the total yield which amounts to 1.7 WETH and is 3417 MORPHO tokens.

At current MORPHO prices, that is about USD 6800.

In the MorphoYearnOGWETHStrategy, we should:

  1. Add a new variable to keep track of the URD

  2. Add a new function to claim and also to send the received MORPHO tokens out.

We can do something similar to:

Then, set the address and implement a function send out the MORPHO tokens.

References

https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/strategies/mainnet/MorphoYearnOGWETH.sol

Proof of Concept

Proof of Concept

Was this helpful?