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(uint256amount)internaloverridereturns(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:
The strategy calculates sharesNeeded using autoEth.convertToShares(amount) which rounds down
The strategy redeems these shares using autoEth.redeem(sharesNeeded, ...)
Due to rounding, the redeemed WETH amount (wethRedeemed) is less than the requested amount
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:
amount must be the total amount being allocated to the strategy.
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.