58324 sc high incorrect return value in deallocate function leads to permanent fund locking in mytstrategy implementations
Submitted on Nov 1st 2025 at 09:18:23 UTC by @fullstop for Audit Comp | Alchemix V3
Report ID: #58324
Report Type: Smart Contract
Report severity: High
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/strategies/mainnet/TokeAutoEth.sol
Impacts:
Permanent freezing of funds
Description
Brief/Intro
The _deallocate function in several strategy contracts, most notably TokeAutoEthStrategy and TokeAutoUSDStrategy, incorrectly returns the requested amount instead of the actual asset amount redeemed (wethRedeemed or usdcRedeemed). This bug is triggered when the "dust-handling" logic is activated by a deallocation request that is very close to the strategy's total holdings. The VaultV2 (which trusts this return value) only withdraws the reported amount, causing the excess redeemed assets to be permanently locked and lost in the strategy contract.
Vulnerability Details
The MYTStrategy contract requires child contracts to implement the _deallocate function. In the TokeAutoEthStrategy and TokeAutoUSDStrategy contracts, this function contains a "dust-handling" feature designed to redeem all remaining shares if the requested deallocation is close to the total shares held.
This logic itself is sound, but its interaction with the function's return value is flawed.
Let's analyze TokeAutoEthStrategy.sol's _deallocate function:
Calculates Share Difference: The contract calculates sharesNeeded based on the requested amount and compares it to actualSharesHeld.
Triggers Dust Logic: It enters a conditional block if the difference is small:
if (shareDiff <= 1e18).Redeems All: Inside this block, it sets
sharesNeeded = actualSharesHeld, forcing the strategy to redeem 100% of its shares from the rewarder and autoEth vault.Calculates Actual Redemption: The contract correctly calculates the actual amount of WETH received from this 100% redemption:
uint256 wethRedeemed = wethBalanceAfter - wethBalanceBefore;.
5, The Flaw: The function then proceeds to its end, but instead of using the wethRedeemed value, it does the following:
TokenUtils.safeApprove(address(weth), msg.sender, amount);: It approves the original requested amount for withdrawal by the VaultV2.
return amount;: It reports to the VaultV2 that the original requested amount has been successfully deallocated.
This exact logic flaw is duplicated in TokeAutoUSDStrategy.sol's _deallocate function. It also has the if (shareDiff <= 1e18) block, calculates usdcRedeemed, but then incorrectly approves and returns the requested amount.
Impact Details
This vulnerability leads to a permanent loss of user funds locked within the strategy contract.
Here is the attack/exploit scenario:
A deallocation is requested from the VaultV2 with an amount that is very close (but not equal) to the strategy's total realAssets.
The strategy's _deallocate is called. The "dust" logic triggers, causing the strategy to redeem 100% of its shares, resulting in an actual redeemed amount (wethRedeemed) that is greater than the requested amount.
The strategy incorrectly returns amount to the VaultV2.
The VaultV2 trusts this return value and updates its internal accounting, believing amount was deallocated.
The VaultV2 calls
transferFrom(strategy, address(this), amount)to pull the assets.The VaultV2 only pulls the amount it was told, as this is all the strategy safeApproved.
Result: The excess funds (i.e.,
wethRedeemed - amount) are left stranded in the strategy contract. The VaultV2's accounting is consistent (from its perspective), and it has no mechanism to recover these orphaned funds. The funds are permanently lost.
References
Vulnerable Contract 1: src/strategies/mainnet/TokeAutoEth.sol
Vulnerable Contract 2: src/strategies/mainnet/TokeAutoUSDStrategy.sol
Proof of Concept
Proof of Concept
A runnable Proof of Concept test, test_reproduce_locked_funds_vulnerability, was added to TokeAutoETHStrategy.t.sol and successfully passed, confirming the vulnerability.
Test Logic:
Setup: The test first allocates 1e18 (1 WETH) to the TokeAutoEthStrategy from the main VaultV2.
Trigger: It calculates the strategy's realAssets and defines an amountToDeallocate that is slightly less than realAssets (e.g., realAssets - 3e13), ensuring the requested amount is small enough to pass the final require check but large enough to trigger the "dust" logic.
Bug Execution: The test calls IVaultV2(vault).deallocate(..., amountToDeallocate).
This forces the strategy's _deallocate to hit the if (shareDiff <= 1e18) block.
The strategy redeems 100% of its shares, receiving the full wethRedeemed.
The strategy incorrectly approves and returns the requested amountToDeallocate.
Result & Validation:
The VaultV2 pulls only the requested amountToDeallocate.
The test then checks the WETH balance of the strategy contract: uint256 lockedFunds = IERC20(testConfig.vaultAsset).balanceOf(strategy);
The test's final assertion, assertGt(lockedFunds, 0, "Funds should be locked in the strategy contract");, passes, proving that funds (wethRedeemed - amountToDeallocate) are left locked in the strategy contract.
Was this helpful?