56494 sc insight gas optimization redundant external calls in strategy deallocate functions
Submitted on Oct 16th 2025 at 19:36:39 UTC by @Snuggle for Audit Comp | Alchemix V3
- Report ID: #56494
- Report Type: Smart Contract
- Report severity: Insight
- Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi\\_audit/src/strategies/arbitrum/EulerARBWETHStrategy.sol
- Impacts:
Description
Brief/Intro
Multiple strategy contracts in the Alchemix V3 protocol contain inefficient gas usage patterns in their _deallocate functions. These contracts perform redundant external calls to TokenUtils.safeBalanceOf() when they already have the balance value cached in memory. This leads to unnecessary gas consumption without providing additional security benefits. The same issue exists across 14 strategy contracts across all networks.
Vulnerability Details
- Multiple strategy contracts contain redundant external calls in their
_deallocatefunctions - The pattern involves calling
TokenUtils.safeBalanceOf()twice when the balance value is already available in memory - This affects 14 strategy contracts across Mainnet, Arbitrum, and Optimism networks
Example from EulerARBWETHStrategy:
function _deallocate(uint256 amount) internal override returns (uint256) {
uint256 wethBalanceBefore = TokenUtils.safeBalanceOf(address(weth), address(this));
vault.withdraw(amount, 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 function calls
TokenUtils.safeBalanceOf()on line 37 to getwethBalanceAfter - Then it calls
TokenUtils.safeBalanceOf()again on line 42 for the require statement - The
wethBalanceAftervariable already contains the exact same value needed for the require check - No state changes occur between these two calls that would affect the balance
Impact Details
- Gas inefficiency: Each redundant
TokenUtils.safeBalanceOf()call consumes unnecessary gas for staticcall - Code maintainability: Redundant external calls make the function less efficient and harder to optimize
- No security benefit: The second call provides no additional security since the balance value is already cached
- Pattern repetition: The same inefficient pattern exists across multiple strategy contracts
- Severity: Insight (gas optimization issue, not a security vulnerability)
Affected Contracts List
Mainnet Strategies (7 contracts):
src/strategies/mainnet/EulerUSDCStrategy.solsrc/strategies/mainnet/EulerWETHStrategy.solsrc/strategies/mainnet/MorphoYearnOGWETH.solsrc/strategies/mainnet/PeapodsETH.solsrc/strategies/mainnet/PeapodsUSDC.solsrc/strategies/mainnet/TokeAutoEth.solsrc/strategies/mainnet/TokeAutoUSDStrategy.sol
Arbitrum Strategies (4 contracts):
src/strategies/arbitrum/EulerARBWETHStrategy.solsrc/strategies/arbitrum/EulerARBUSDCStrategy.solsrc/strategies/arbitrum/AaveV3ARBUSDCStrategy.solsrc/strategies/arbitrum/AaveV3ARBWETHStrategy.sol
Optimism Strategies (3 contracts):
src/strategies/optimism/MoonwellUSDCStrategy.solsrc/strategies/optimism/MoonwellWETHStrategy.solsrc/strategies/optimism/StargateEthPoolStrategy.sol
Total: 14 strategy contracts affected across all networks
Recommendation
Replace redundant external calls with cached values. The same optimization pattern applies to all 14 affected contracts:
Example Fix for EulerARBWETHStrategy.sol:
function _deallocate(uint256 amount) internal override returns (uint256) {
uint256 wethBalanceBefore = TokenUtils.safeBalanceOf(address(weth), address(this));
vault.withdraw(amount, 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(wethBalanceAfter >= amount, "Strategy balance is less than the amount needed");
TokenUtils.safeApprove(address(weth), msg.sender, amount);
return amount;
}
Key Change: Replace require(TokenUtils.safeBalanceOf(address(weth), address(this)) >= amount, ...) with require(wethBalanceAfter >= amount, ...)
This optimization pattern should be applied to all 14 affected contracts listed above, maintaining the same security guarantees while eliminating redundant gas consumption.
Proof of Concept
Proof of Concept
Steps to Reproduce:
- Call
_deallocatefunction in any affected strategy contract with any valid amount - Observe that
TokenUtils.safeBalanceOf()is called multiple times for the same balance check - Verify that the cached balance value could be used instead of making additional external calls