58409 sc high high arithmetic underflow in mytstrategy sol s deallocate check prevents yield withdrawal
Submitted on Nov 2nd 2025 at 02:08:55 UTC by @chief_hunter888 for Audit Comp | Alchemix V3
Report ID: #58409
Report Type: Smart Contract
Report severity: High
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/MYTStrategy.sol
Impacts:
Theft of unclaimed yield
Description
[HIGH] Arithmetic Underflow in MYTStrategy.sol's deallocate() Prevents Yield Withdrawal
MYTStrategy.sol's deallocate() Prevents Yield WithdrawalSummary
The MYTStrategy base contract is designed to work with yield-bearing strategies that wrap ERC4626 vaults (e.g., MorphoYearnOGWETH, EulerWETH). These strategies deposit assets (e.g., WETH) and receive shares that accrue value over time. When the Alchemix vault later deallocates funds (for user withdrawals, rebalancing, or other operations), it should be able to withdraw the original principal plus any earned yield.
However, the deallocate() function in MYTStrategy.sol contains a critical arithmetic flaw on line 130 that makes withdrawing yield impossible:
function deallocate(bytes memory data, uint256 assets, ...) external returns (...) {
uint256 oldAllocation = abi.decode(data, (uint256)); // Line 128: Original deposit amount
uint256 amountDeallocated = _deallocate(assets); // Line 129: Amount withdrawn (can include yield)
uint256 newAllocation = oldAllocation - amountDeallocated; // Line 130: ❌ UNDERFLOW!
// ...
}The Problem:
oldAllocationis decoded from thedataparameter passed by the vault (e.g., 100 WETH)The
dataparameter contains stale accounting - it reflects the allocation amount at some previous point in time. On-chainoldAllocationtracks the original deposit amount (e.g., 100 WETH). See allocate flow.amountDeallocatedis the actual amount withdrawn, which includes accrued yield (e.g., 105 WETH after 5% yield)When yield exists:
100 WETH - 105 WETHcauses arithmetic underflow and reverts
Critical Issue: The data parameter has no freshness guarantee. There's no timestamp or block number to validate that oldAllocation reflects current reality. Even if the vault could update data externally (via oracle or manual update), yield earned between updates remains permanently inaccessible because the accounting is always behind the actual accrued value.
Impact
Critical: This bug fundamentally breaks the core purpose of yield-bearing strategies - earned yield cannot be withdrawn by the protocol.
Yield Permanently Frozen Between Updates: The vault can only deallocate up to the amount encoded in the
dataparameter. Any yield earned beyond that amount causes arithmetic underflow. Even ifdatacould theoretically be updated externally:There's no freshness validation (no block number or timestamp)
Yield accrues continuously, but
dataupdates are discreteThe gap between actual value and recorded
oldAllocationcreates permanently locked yieldFor strategies earning 5-20% APY, significant value becomes inaccessible
Complete Protocol DoS When Attempting Full Withdrawal: Any attempt to deallocate the actual current value (principal + yield) causes an arithmetic underflow revert, making the following operations impossible:
Processing user withdrawal requests for full balances
Vault rebalancing operations to optimize yield
Emergency fund recovery during crises
Strategy migrations or upgrades
Liquidations requiring full collateral withdrawal
Affects ALL Strategies: This bug is in the
MYTStrategy.solbase contract, meaning it affects every strategy that inherits from it:MorphoYearnOGWETHStrategyEulerWETHStrategy/EulerUSDCStrategyPeapodsETHStrategy/PeapodsUSDCStrategyTokeAutoEthStrategy/TokeAutoUSDStrategyAnd all other MYT strategies
Loss of Funds: While the principal remains technically accessible (assuming
datareflects at least the principal), the yield (which could represent millions of dollars across all strategies) is permanently or semi-permanently locked, depending on whether and how frequentlydatacould be updated externally.
Furthermore, in addition to preventing the current programmatic underflow reverts that may occur during deallocation when the yield-bearing token snapshot is outdated, the code should be updated to handle this “out-of-sync” error more gracefully than a Solidity revert. Specifically, an early check should be introduced at the start of the deallocate function to verify that previousAllocationData is sufficiently fresh, using the block number at which it was last refreshed. This ensures all bookkeeping values are up-to-date and prevents the error from being triggered.
Implementing this check also guarantees that the maximum possible amount can be withdrawn from the strategy via deallocation at any given time. Without fresh off-chain oracle data, any strategy will always have a portion of yield effectively frozen, because the amount that can be deallocated is limited to the last snapshot recorded in the data field.
Proof of Concept
Scenario: Alchemix vault allocates to MorphoYearnOGWETH strategy, yield accrues, then protocol attempts to deallocate.
Test Evidence: See test_MYTStrategy_arithmetic_underflow_with_yield() in:
src/test/strategies/MockYieldStrategy.t.sol(clean POC)src/test/strategies/MorphoYearnOGWETHStrategy.t.sol(real strategy)
How to run
Likelihood
Certainty: 100% — This will occur in every yield-bearing strategy deployment:
Yield Accrual is Expected: The entire purpose of these strategies is to earn yield (5-20% APY). Yield accrues continuously in the underlying ERC4626 vaults.
Data Staleness is Inevitable: The
dataparameter passed todeallocate()contains a snapshot of allocation at some previous time. Unless there's a mechanism to update this data on every block (which would be prohibitively expensive), the encodedoldAllocationwill always lag behind the actualrealAssets()value.Affects Normal Operations: Any protocol operation attempting to deallocate the full current value (principal + accrued yield) will hit this underflow:
User withdrawal requests for full balances
Vault rebalancing to move funds between strategies
Emergency deallocations during market stress
Strategy migrations
No Freshness Validation: The contract has no mechanism to verify that
datais fresh. There's no block number, timestamp, or staleness check. Even if an external system could updatedata, the contract would blindly trust any value passed in.Already Deployed: If these strategies are in production, yield is accruing right now and the gap between
oldAllocationandrealAssets()is growing with every block.
Recommendation
Option 1: Add Freshness Validation (Recommended)
Update the data structure to include freshness metadata and validate it:
Option 2: Fix Arithmetic Logic (Simpler, but doesn't address staleness)
Option 3: Vault-Level Solution
Implement a mechanism in the vault to query strategy.realAssets() and update the allocation data before calling deallocate(), ensuring oldAllocation always reflects current value including yield.
Root Cause
Two interrelated design flaws:
Stale Data Without Freshness Validation: The
dataparameter encodes allocation amounts but has no mechanism to ensure this data is current:No block number or timestamp to validate freshness
No requirement that
oldAllocationreflectsrealAssets()before deallocationThe vault could theoretically update
dataexternally (oracle, manual update), but the contract has no way to enforce or verify this
Arithmetic Assumes No Yield: The subtraction
oldAllocation - amountDeallocated(line 130) assumesamountDeallocated ≤ oldAllocation. This works for:Non-yield-bearing strategies
Partial withdrawals less than original deposit
But fails for yield-bearing strategies where
amountDeallocatedcan include accrued yield
The fundamental issue: The contract treats allocations as static amounts, but yield-bearing strategies have dynamic, appreciating values. Without fresh accounting data, any yield earned since the last update becomes inaccessible.
Severity
HIGH / CRITICAL
Impact: Critical - Yield permanently or semi-permanently frozen, protocol-level DoS for operations requiring full withdrawals
Likelihood: Certain - Data staleness is inevitable unless updated every block (prohibitively expensive); affects 100% of yield-bearing strategy usage
Affected Code: Base contract affecting all inheriting strategies (10+ production strategies)
Protocol Impact:
Cannot process user withdrawal requests for full balances
Cannot fully rebalance between strategies
Yield earned between data updates is inaccessible
Emergency operations blocked
Financial Impact: Yield losses proportional to update frequency and APY rates (5-20% APY on potentially millions of TVL)
References
Vulnerable Code:
File:
src/MYTStrategy.solFunction:
deallocate()(lines 119-133)Bug Location: Line 130
Affected Strategies (Inheriting from MYTStrategy):
src/strategies/mainnet/MorphoYearnOGWETH.solsrc/strategies/mainnet/EulerWETHStrategy.solsrc/strategies/mainnet/EulerUSDCStrategy.solsrc/strategies/mainnet/PeapodsETH.solsrc/strategies/mainnet/PeapodsUSDC.solsrc/strategies/mainnet/TokeAutoEth.solsrc/strategies/mainnet/TokeAutoUSDStrategy.solAll other strategies inheriting
MYTStrategy
Proof of Concept Tests:
src/test/strategies/MockYieldStrategy.t.sol::test_MYTStrategy_arithmetic_underflow_with_yield()src/test/strategies/MorphoYearnOGWETHStrategy.t.sol::test_MYTStrategy_arithmetic_underflow_with_yield()
Documentation:
Proof of Concept
Proof of Concept
Mock Contract that mocks a Yield Accruing Strategy for testing:
the mock yield strategy test:
Just 2 tests to add to MorphoYearnOGWETHStrategy.sol to illustrate the limitation in the withdrawal cycle
Was this helpful?