58067 sc high asymmetric deallocation in tokeautoethstrategy leads to permanent weth funds stuck in strategy

Submitted on Oct 30th 2025 at 12:15:42 UTC by @dobrevaleri for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #58067

  • 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

When the TokeAutoEthStrategy::_deallocate() function determines that remaining shares after withdrawal would be small (≤ 1e18), it withdraws all shares to avoid rounding issues. However, this creates an asymmetry where more assets are withdrawn than the requested amount, but only the requested amount is approved for transfer back to the vault. The excess WETH remains permanently stuck in the strategy contract.

Vulnerability Details

The vulnerability exists in the TokeAutoEthStrategy::_deallocate() function at lines 72-80. When the difference between held shares and needed shares (shareDiff) is less than or equal to 1e18, the strategy withdraws ALL shares instead of just the required amount:

function _deallocate(uint256 amount) internal override returns (uint256) {
    uint256 sharesNeeded = autoEth.convertToShares(amount);
    uint256 actualSharesHeld = rewarder.balanceOf(address(this));
    uint256 shareDiff = actualSharesHeld - sharesNeeded;
    if (shareDiff <= 1e18) {
        // account for vault rounding up
@>      sharesNeeded = actualSharesHeld;
    }
    // withdraw shares, claim any rewards
@>  rewarder.withdraw(address(this), sharesNeeded, true);
    uint256 wethBalanceBefore = TokenUtils.safeBalanceOf(address(weth), address(this));
    autoEth.redeem(sharesNeeded, address(this), 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(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 issue manifests in the following sequence:

  1. The strategy withdraws actualSharesHeld shares (all shares) from the rewarder

  2. The autoEth.redeem() call returns all underlying WETH assets corresponding to ALL shares

  3. However, the function only approves and returns the originally requested amount

  4. The excess WETH (representing the shareDiff) remains in the strategy contract

Only the requested amount is approved for transfer, leaving the excess permanently inaccessible.

Impact Details

Excess WETH tokens become permanently stuck in the strategy contract with no mechanism for recovery.

References

https://github.com/alchemix-finance/v3-poc/blob/a192ab313c81ba3ab621d9ca1ee000110fbdd1e9/src/strategies/mainnet/TokeAutoEth.sol#L67-L87

Proof of Concept

Proof of Concept

Was this helpful?