Smart contract unable to operate due to lack of token funds
Description
Summary
The TokeAutoEth strategy's _allocate() function approves only the requested amount to the router but then calls router.depositMax(), which attempts to deposit the entire WETH balance of the strategy contract. When the strategy's balance exceeds the approval EVEN BY 1 WEI (due to residual rewards, previous withdrawal leftovers, or donations), the transaction reverts.
Vulnerable Code
Strategy Allocation (Approves only amount)
File: src/strategies/mainnet/TokeAutoEth.sol
function_allocate(uint256amount)internaloverridereturns(uint256){require(TokenUtils.safeBalanceOf(address(weth),address(this))>= amount,"Strategy balance is less than amount");// Only approves `amount` to router TokenUtils.safeApprove(address(weth),address(router), amount);// But depositMax() tries to deposit entire balance!uint256 shares = router.depositMax(autoEth,address(this),0); TokenUtils.safeApprove(address(autoEth),address(rewarder), shares); rewarder.stake(address(this), shares);return amount;}
Step 3: Router attempts to pull more than approved
How Extra Balance Accumulates
Reward Claims: When rewarder.withdraw(claim=true) is called during deallocations, extra reward tokens are sent to the strategy
Withdrawal Leftovers: Slippage or rounding in autoEth.redeem() may leave dust amounts
Direct Transfers: Accidental or malicious transfers of >= 1 wei of WETH to the strategy will PERMANENTLY cause the strategy to always revert during deallocation.
Root Cause
The AutopilotRouter.depositMax() function uses balanceOf(msg.sender) to determine the deposit amount, completely ignoring the approval. This creates a fundamental mismatch when any extra balance exists in the strategy contract.
Impact
Severity Justification
This will render the startegy completely unuseable as it will always revert.
Recommended Fix
Approve Entire Balance
If using depositMax() is required, approve the entire balance instead:
function depositMax(
IAutopool vault,
address to,
uint256 minSharesOut
) public payable override returns (uint256 sharesOut) {
IERC20 asset = IERC20(vault.asset());
// Uses msg.sender's ENTIRE balance, not approved amount
uint256 assetBalance = asset.balanceOf(msg.sender);
uint256 maxDeposit = vault.maxDeposit(to);
uint256 amount = maxDeposit < assetBalance ? maxDeposit : assetBalance;
// Attempts to pull `amount` (which may exceed approval)
pullToken(asset, amount, address(this));
approve(IERC20(vault.asset()), address(vault), amount);
return deposit(vault, to, amount, minSharesOut);
}
// Strategy has 100.0000001 WETH (100 from allocation + 0.0000001 from rewards or rounding discrepancies or even donations)
// Allocator requests to allocate 100 WETH
TokenUtils.safeApprove(address(weth), address(router), amount); // Approves 100 WETH