58360 sc low round down calculation in converttoshares leads to deallocation failure in tokeautoeth strategy

Submitted on Nov 1st 2025 at 15:32:38 UTC by @dobrevaleri for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #58360

  • Report Type: Smart Contract

  • Report severity: Low

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

  • Impacts:

    • Contract fails to deliver promised returns, but doesn't lose value

Description

Brief/Intro

The TokeAutoEthStrategy::_deallocate() function fails due to insufficient shares being calculated by the external autoEth.convertToShares() function, which always rounds down. This results in the strategy being unable to redeem enough WETH to satisfy the requested deallocation amount, causing all withdrawal operations to revert.

Vulnerability Details

The issue occurs in the TokeAutoEthStrategy::_deallocate() function:

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) {
        sharesNeeded = actualSharesHeld;
    }
    
    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;
    
    require(TokenUtils.safeBalanceOf(address(weth), address(this)) >= amount, "Strategy balance is less than the amount needed");
    // ...
}

The problem is that Tokemak AutopoolETH vault's convertToShares() rounds down for favor of the user:

When called from the public convertToShares(uint256 assets) function, it uses Math.Rounding.Down:

This creates a scenario where:

  1. The strategy calculates sharesNeeded using autoEth.convertToShares(amount) which rounds down

  2. The strategy redeems these shares using autoEth.redeem(sharesNeeded, ...)

  3. Due to rounding, the redeemed WETH amount (wethRedeemed) is less than the requested amount

  4. The final require statement fails: require(TokenUtils.safeBalanceOf(address(weth), address(this)) >= amount, "Strategy balance is less than the amount needed")

The strategy attempts to mitigate this with a check for when shareDiff <= 1e18, which actually allows for selecting amount in such way that the shares diff to be less than 1e18, and the deallocation will pass.

So the only way for the deallocate to pass is:

  1. amount must be the total amount being allocated to the strategy.

  2. Manually call uint256 shares = autoEth.convertToShares(amount).

  3. Manually call uint256 amountToDeallocate = autoEth.convertToAssets(shares-0.5e18)

  4. Call the deallocate function with amountToDeallocate

Impact Details

All partial and full deallocations will fail, except the one described above. This will cause the strategy to work not as expected.

References

https://github.com/Tokemak/v2-core-pub/blob/de163d5a1edf99281d7d000783b4dc8ade03591e/src/vault/AutopoolETH.sol#L545-L547

Proof of Concept

Proof of Concept

The first test demonstrates that deallocation consistently fails with the error "Strategy balance is less than the amount needed" due to the rounding issue in convertToShares().

The second test shows the only way in which the deallocate will not fail.

Was this helpful?