# 58357 sc low permanent freezing of tokeautoeth strategy rewards in myt vault

**Submitted on Nov 1st 2025 at 15:21:56 UTC by @call\_me\_rp for** [**Audit Comp | Alchemix V3**](https://immunefi.com/audit-competition/alchemix-v3-audit-competition)

* **Report ID:** #58357
* **Report Type:** Smart Contract
* **Report severity:** Low
* **Target:** <https://github.com/alchemix-finance/v3-poc/blob/immunefi\\_audit/src/strategies/mainnet/TokeAutoEth.sol>
* **Impacts:**
  * Permanent freezing of unclaimed royalties
  * Permanent freezing of unclaimed yield

## Description

## Summary

Rewards generated by the `TokeAutoEth strategy` are claimed into the `MYT vault` (address(MYT)). Once received or staked under the MYT address, these rewards (TOKE token) cannot be withdrawn, transferred, or reallocated. As a result, all rewards are permanently stuck in the MYT contract.

## Description

When `claimRewards()` is called, the strategy executes:

```solidity
MYTStrategy.sol::
    function claimRewards() public virtual returns (uint256) {
        require(!killSwitch, "emergency");
        _claimRewards();
    }

It calls into TokeAutoEth.sol::
    function _claimRewards() internal override returns (uint256 rewardsClaimed) {
        rewardsClaimed = rewarder.earned(address(this));
>>      rewarder.getReward(address(this), address(MYT), false); // transfer all TOKE token rewards to morpho vault or get staked on myt address
    }

```

* The Tokemak rewarder then either transfers the TOKE tokens or stakes them with recipient = address(MYT):

\[Link to Tokemak rewarder code]]\(<https://github.com/Tokemak/v2-core-pub/blob/de163d5a1edf99281d7d000783b4dc8ade03591e/src/rewarders/AbstractRewarder.sol#L306C4-L331C6>)

```solidity
    function _getReward(address account, address recipient) internal {
        ...

        // if NOT toke, or staking is turned off (by duration = 0), just send reward back
        if (rewardToken != tokeAddress || tokeLockDuration == 0) {
            rewards[account] = 0;
            emit RewardPaid(account, recipient, reward);

>>          IERC20(rewardToken).safeTransfer(recipient, reward); // transfer tokens to myt
        } else if (accToke.isStakeableAmount(reward)) {
            rewards[account] = 0;
            emit RewardPaid(account, recipient, reward);
            // authorize accToke to get our reward Toke
            LibAdapter._approve(IERC20(tokeAddress), address(accToke), reward);

            // stake Toke
>>          accToke.stake(reward, tokeLockDuration, recipient); // Or stake token as myt as owner
        }
    }

```

Since the MYT vault has no functionality to transfer, withdraw, or re-allocate these TOKE rewards, all claimed rewards becomes permanently locked.

Additionally, anyone can call claimRewards(), meaning the freeze can occur at any time, even if rewards are small and unintended.

## Impact

All TOKE rewards generated by the strategy become locked into myt.

## Recommendation

Claim rewards to the strategy instead of the MYT vault:

`rewarder.getReward(address(this), address(this), false);`

Then implement appropriate handling in the strategy:

Optionally swap to underlying asset, OR sends to owner

Introduce unstake() too to get staked toke.

## Proof of Concept

## Proof of Concept

## PoC Summary

Since the real rewarder contract on mainnet distributes rewards based on live protocol state, the test environment cannot naturally reproduce accrued rewards. To demonstrate the issue in isolation, we replaced the rewarder contract implementation at its address using `vm.etch` and pointed its rewardToken storage slot to a mock ERC20 token. The mock rewarder is intentionally minimal and always returns a fixed reward amount (5e18) for earned() and sends that amount in getReward().

By doing this, we simulate the presence of claimable rewards without altering the whole rewarder logic. When claimRewards() is executed, the adapter puts recipient as myt, and then those reward token(TOKE token) will always stuck in vault/MYT.

1. Add below test case and interfaces into `TokeAutoEthStrategy.t.sol`:

```solidity
import {TestERC20} from "../mocks/TestERC20.sol";

interface IERC20 {
    function approve(address spender, uint256 amount) external returns (bool);
    function balanceOf(address a) external view returns (uint256);
    function transfer(address to, uint256 amount) external returns (bool);
}

interface IBaseRewarder {
    function getReward(address account, address recipient, bool claimExtras) external;
    function earned(address account) external view returns (uint256);
    function rewardToken() external view returns (IERC20);
}

contract MockRewarder is IBaseRewarder {
    /// @notice The reward token distributed by the rewarder(TOKE token)
    IERC20 public rewardToken;

    mapping(address account => uint256) private _balances;

    constructor(IERC20 _rewardToken) {
        rewardToken = _rewardToken;
    }

    /// @notice Always return 5e18 as "earned" — simulates pending rewards
    function earned(address account) external view override returns (uint256) {
        return 5e18;
    }

    /// @notice Sends 5e18 reward tokens to the vault (recipient -> myt)
    function getReward(address account, address recipient, bool claimExtras) external override {
        IERC20(rewardToken).transfer(recipient, 5e18);
    }

    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

}

    function test_rewards_stuck_into_myt() public {
        // --- Setup: Allocate funds into the strategy ---
        uint256 amountToAllocate = 100 ether;
        vm.startPrank(vault);
        deal(testConfig.vaultAsset, strategy, amountToAllocate);
        bytes memory prevAllocationAmount = abi.encode(0);
        IMYTStrategy(strategy).allocate(prevAllocationAmount, amountToAllocate, "", address(vault));
        vm.stopPrank();

        IBaseRewarder rewarder = IBaseRewarder(REWARDER);

        vm.startPrank(address(rewarder));
        // --- Step 1: Deploy a mock reward token (this simulates the underlying protocol reward token) ---
        TestERC20 rewardToken = new TestERC20(100e18, 18);
        vm.stopPrank();

        // --- Step 2: Deploy our MockRewarder which always "returns" 5e18 earned ---
        MockRewarder mock = new MockRewarder(IERC20(address(rewardToken)));

        // --- Step 3: Overwrite the real rewarder contract code with our mock implementation ---
        vm.etch(REWARDER, address(mock).code);

        // --- Step 4: Patch storage so MockRewarder.rewardToken points to our mock reward token ---
        // rewardToken is the FIRST storage slot in MockRewarder → slot index = 0
        vm.store(
            REWARDER,
            bytes32(uint256(0)),
            bytes32(uint256(uint160(address(rewardToken))))
        );

        // --- Step 5: Fund the rewarder so 'getReward()' can actually transfer tokens ---
        deal(address(rewardToken), REWARDER, 10 ether);

        // --- Step 6: Trigger the strategy reward claiming logic ---
        IMYTStrategy(strategy).claimRewards();

        // This shows that rewards end up stuck inside MYT / vault and cannot be recovered.
        uint mytbal= rewardToken.balanceOf(vault);
        assertEq(mytbal, 5e18);
    }

```

2. run by `forge test --mt test_rewards_stuck_into_myt -vvvv`
3. output:

```solidity
TestERC20::balanceOf(MockMYTVault: [0x522B3294E6d06aA25Ad0f1B8891242E335D3B459]) [staticcall]
    │   └─ ← [Return] 5000000000000000000 [5e18]
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://reports.immunefi.com/alchemix-v3/58357-sc-low-permanent-freezing-of-tokeautoeth-strategy-rewards-in-myt-vault.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
