
In high-frequency on-chain multihop arbitrage and MEV competition, even small differences in gas efficiency can determine the outcome.
Suppose a large sell order creates a 0.015 WETH gross arbitrage opportunity for a long-tail token between Uniswap V2 and Aerodrome, on Base or Ethereum mainnet. Bots A and B detect it in the same millisecond-scale step and calculate identical optimal input amounts.
Bot A calls the standard UniswapV2Router02 contract and withdraws every token balance after each arbitrage. Bot B uses a custom native flash-loan multihop contract, bypasses the router to interact directly with liquidity pools, and deliberately leaves 1 wei in token balance slots when withdrawing profits.
Bot A consumes 265,000 gas. After priority fees, its net return becomes negative, or its high gas limit delays inclusion until slippage causes a revert. Bot B’s lean instructions and prewarmed storage consume only 173,000 gas: over 90,000 gas saved in one transaction. It can offer the builder a 30% higher bribe, secure top-of-block inclusion, and retain a net profit.
Under EVM Yellow Paper rules, gas optimization is a precise exercise in storage (SSTORE), external calls (CALL), and memory allocation, rather than syntactic shortcuts. Using our production arbitrage contract on Base mainnet (evm-arbitrage/src/FlashMultiHopArb.sol), this article examines 1-wei storage prewarming, direct pool calls that bypass routers, and nested singleton callback locks to save at least 50,000 gas per high-frequency multihop trade.
Gas Optimization: Three Main Opportunities
Before examining the code, compare the sources of gas consumption and their corresponding optimizations:
| Dimension | Conventional high-gas implementation | Production optimization (FlashMultiHopArb) |
Savings and mechanism |
|---|---|---|---|
| Storage slot temperature | Withdraw all profits after each trade; balance returns to 0 |
Keep 1 wei with withdrawProfitKeepDust |
Save 17,100 gas per hop (avoid cold 0 -> nonzero writes; 51,300 gas across 3 hops) |
| Routing | Call UniswapV2Router02.swapExactTokensForTokens |
Transfer directly to Pair and call pair.swap |
35%+ less bytecode overhead (remove redundant encoding, memory copies, and proxy calls) |
| Reentrancy and callbacks | OpenZeppelin ReentrancyGuard with extra storage slot |
Singleton address lock activeCallbackPool, also authenticating callers |
Save 5,000+ gas (one slot handles reentrancy and callback-source validation) |
| Transfer-tax compatibility | Trust amountIn; high-tax tokens revert |
Verify balanceOf(pool) - reserveIn |
Zero extra calls (existing reserves support tax defense and exact accounting) |
Optimization 1: SSTORE Costs and 1-Wei Dust Prewarming
EIP-2200 and EIP-2929 introduced tiered pricing for Ethereum’s SSTORE instruction. Understanding the cost of changing a slot from zero to nonzero is fundamental to highly optimized contracts.
1. The Actual Gas Cost of Storage Writes
An IERC20.transfer modifies the token contract’s mapping(address => uint256) balances. The storage transitions cost:
- Cold storage write:
0 -> nonzero:- Base access: 2,100 gas for the first access to a cold slot.
- Slot initialization: 20,000 gas.
- Minimum total: 22,100 gas.
- Warm storage modification:
nonzero -> nonzero:- Base access: 100 gas for a warmed slot.
- Value update: 2,900 gas if the value changes and remains nonzero.
- Minimum total: approximately 3,000 gas.
Changing an empty address balance from 0 to a positive amount costs 17,100 gas more than modifying an address that already holds a small balance.
2. Three Cold Writes in a Multihop Trade
In a typical three-hop triangular arbitrage, such as WETH → TokenA → TokenB → WETH:
- Buying TokenA changes the contract’s TokenA
balanceOffrom0to nonzero: the first cold write, 20,000 gas. - Selling TokenA for TokenB changes its TokenB
balanceOffrom0to nonzero: the second cold write, 20,000 gas. - Selling TokenB for WETH changes its WETH profit slot from
0to nonzero: the third cold write, 20,000 gas.
If the bot owner withdraws every token after each trade, all three slots incur cold writes again at the next opportunity, wasting 17, 100 × 3 = 51, 300 Gas.
3. Implementing withdrawProfitKeepDust
FlashMultiHopArb uses dust holding to avoid this cost. Profit withdrawals leave 1 wei (10−18 tokens, effectively zero value) at the contract address, permanently preserving the slot’s nonzero warm state:
// From evm-arbitrage/src/FlashMultiHopArb.sol
/**
* @notice Withdraw profit but retain 1 wei (dust holding), keeping this token's balance slot nonzero.
* Each later trade avoids the 17,100 gas penalty for an ERC20 balance write from 0 to nonzero.
*/
function withdrawProfitKeepDust(address token) external onlyOwner {
uint256 bal = IERC20(token).balanceOf(address(this));
if (bal > 1) {
safeTransfer(token, owner, bal - 1);
}
}
/**
* @notice Clear all tokens, including 1 wei dust (only when abandoning the pair or retiring the contract)
*/
function withdrawToken(address token, uint256 amount) external onlyOwner {
safeTransfer(token, owner, amount);
}
Subsequent arbitrage operations keep all involved ERC-20 balance changes in the cheaper warm-write range, saving at least 50,000 gas per transaction.
Optimization 2: Bypass the Router and Call the Pair Directly
Developers new to DeFi arbitrage often call UniswapV2Router02 from their contracts. The official router is a general-purpose facade designed to protect ordinary frontend users, with many defenses and intermediate steps unnecessary for arbitrage.
1. Four Sources of Router Overhead
Calling router.swapExactTokensForTokens creates these redundant costs:
- Repeated encoding and memory allocation: construct
path[]in the arbitrage contract, thenabi.decodeagain in the router, paying memory-expansion gas. - Extra approval and transfer calls: the router first uses
safeTransferFromto pull tokens from the arbitrage contract into itself, then transfers them into the pool, making two external ERC-20 calls. - Redundant timestamp and slippage checks: arbitrage is atomic and packaged by the searcher with a flash loan for a specific block. The router’s
ensure(deadline)and generic slippage checks add unnecessary computation. - No native fee-on-transfer defense: a long-tail token with a 1% buy tax makes the router’s calculated output differ from the actual pool input, reverting the entire transaction.
2. Direct Pool Interaction (_swapV2)
In evm-arbitrage/src/FlashMultiHopArb.sol, we bypass the router entirely and use IUniswapV2Pair.swap directly:
// From evm-arbitrage/src/FlashMultiHopArb.sol
function _swapV2(
SwapStep memory step,
uint256 amountIn,
uint256 hopIndex
) internal returns (uint256 amountOut) {
IUniswapV2Pair pair = IUniswapV2Pair(step.pool);
(uint256 r0, uint256 r1, ) = pair.getReserves();
bool zeroForOne = step.tokenIn < step.tokenOut;
(uint256 reserveIn, uint256 reserveOut) = zeroForOne ? (r0, r1) : (r1, r0);
// 1. Optimistic transfer: push input tokens directly from this contract to Pair
safeTransfer(step.tokenIn, step.pool, amountIn);
// 2. Core defense: read actual pool balance increase (handles hidden transfer taxes)
uint256 actualAdded = IERC20(step.tokenIn).balanceOf(step.pool) - reserveIn;
// 3. Compute theoretical maximum output after fees from actual received input
uint256 amountInWithFee = actualAdded * step.feeBps;
amountOut = (amountInWithFee * reserveOut) / (reserveIn * 10000 + amountInWithFee);
if (amountOut == 0) revert ZeroOutputOnHop(hopIndex);
// 4. Record output-token balance before receiving the low-level transfer
uint256 balBefore = IERC20(step.tokenOut).balanceOf(address(this));
// 5. Call swap directly without bytecode redirection
pair.swap(
zeroForOne ? 0 : amountOut,
zeroForOne ? amountOut : 0,
address(this),
new bytes(0)
);
// 6. Return the actual principal received for the next hop
amountOut = IERC20(step.tokenOut).balanceOf(address(this)) - balBefore;
}
This minimal implementation provides three advantages:
- Lower gas: removing the routing layer halves the call stack and saves at least 35% gas per swap compared with using the router.
- Adaptive transfer-tax handling:
actualAdded = balanceOf(pool) - reserveInuses the pool’s actual received amount, even with a 2% transfer tax or burn mechanism, preventing overflow or shortfall. - Actual-receipt accounting:
balanceOf(this) - balBeforeestablishes the true next-hop input, eliminating floating-point truncation and rounding discrepancies.
Optimization 3: A Compact Singleton Callback Lock (activeCallbackPool)
Flash-loan arbitrage contracts must reject forged callbacks. An attacker may call uniswapV3FlashCallback directly with fabricated arguments to exploit the contract’s approvals or drain residual funds.
A conventional approach inherits OpenZeppelin ReentrancyGuard. Its separate uint256 private _status slot switches between 1 and 2, requiring another expensive SSTORE. It prevents reentrancy but cannot determine whether the caller is a legitimate flash-loan pool.
1. Combine Reentrancy Protection and Caller Authentication
FlashMultiHopArb uses one address slot, address private activeCallbackPool, as both a reentrancy lock and a live caller allowlist:
// From evm-arbitrage/src/FlashMultiHopArb.sol
// Callback safety lock: prevent malicious contracts forging Flash/Swap callbacks
address private activeCallbackPool;
function executeArbitrage(
address flashPool,
address borrowToken,
bool borrowIsToken0,
uint256 amountBorrow,
uint256 minProfit,
uint256 bribeBps,
SwapStep[] calldata steps
) external onlyOwner returns (uint256 netProfit) {
// ... Assemble initial parameters ...
// Lock to the currently authorized flash-loan pool
activeCallbackPool = flashPool;
// Initiate V3 flash loan
IUniswapV3Pool(flashPool).flash(
address(this),
borrowIsToken0 ? amountBorrow : 0,
borrowIsToken0 ? 0 : amountBorrow,
data
);
// Clear immediately after execution to restore unlocked state
activeCallbackPool = address(0);
// ... Profit accounting and bribe distribution ...
}
// V3 flash-loan callback
function uniswapV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external {
// Reject every caller other than the currently locked flash-loan pool!
if (msg.sender != activeCallbackPool) revert Unauthorized();
// ... Execute multihop swaps ...
}
2. Supporting Nested Callbacks
Multihop trades can nest a swap against another Uniswap V3 pool inside an outer Uniswap V3 flash loan. To support this topology without corrupting the lock, _swapV3 saves and restores it through a stack variable:
// From evm-arbitrage/src/FlashMultiHopArb.sol
function _swapV3(
SwapStep memory step,
uint256 amountIn,
uint256 hopIndex
) internal returns (uint256 amountOut) {
bool zeroForOne = step.tokenIn < step.tokenOut;
// Save the parent callback lock and temporarily authorize this V3 swap pool
address prevPool = activeCallbackPool;
activeCallbackPool = step.pool;
uint256 balBefore = IERC20(step.tokenOut).balanceOf(address(this));
IUniswapV3Pool(step.pool).swap(
address(this),
zeroForOne,
int256(amountIn),
zeroForOne ? MIN_SQRT_RATIO_PLUS_ONE : MAX_SQRT_RATIO_MINUS_ONE,
abi.encode(step.tokenIn)
);
// Restore the parent lock
activeCallbackPool = prevPool;
amountOut = IERC20(step.tokenOut).balanceOf(address(this)) - balBefore;
if (amountOut == 0) revert ZeroOutputOnHop(hopIndex);
}
This design closes the callback authorization flow at very low gas cost.
Foundry Test Evidence
In evm-arbitrage/test/FlashMultiHopArb.t.sol, Foundry fork tests verify these optimizations against the Base mainnet environment:
// From evm-arbitrage/test/FlashMultiHopArb.t.sol
function test_RealFlashArbitrageRepaysFeeAndWithdrawsProfit() public {
_createPriceGap(); // Simulate a real mainnet price gap
uint256 flashBalanceBefore = IERC20(WETH).balanceOf(FLASH_POOL);
uint256 gasBefore = gasleft();
// Execute arbitrage
uint256 profit = _execute(0.001 ether, 0);
uint256 executionGas = gasBefore - gasleft();
uint256 fee = _flashFee();
// Assert loan principal and fee repaid, with profit accounted correctly
require(IERC20(WETH).balanceOf(FLASH_POOL) == flashBalanceBefore + fee, "Flash fee not repaid");
require(IERC20(WETH).balanceOf(address(arb)) == profit, "Incorrect executor profit");
// Verify dust-preserving withdrawal leaves exactly 1 wei
arb.withdrawProfitKeepDust(WETH);
require(IERC20(WETH).balanceOf(address(arb)) == 1, "Dust missing");
// Verify complete withdrawal returns the balance to zero
arb.withdrawToken(WETH, 1);
require(IERC20(WETH).balanceOf(address(arb)) == 0, "Dust withdrawal failed");
}
function test_RejectsUnsolicitedFlashAndSwapCallbacks() public {
// Assert unauthorized forged callbacks are rejected, preventing reentrancy and spoofing
vm.prank(ATTACKER);
vm.expectRevert(abi.encodeWithSelector(FlashMultiHopArb.Unauthorized.selector));
arb.uniswapV3FlashCallback(0, 0, "");
vm.prank(ATTACKER);
vm.expectRevert(abi.encodeWithSelector(FlashMultiHopArb.Unauthorized.selector));
arb.uniswapV3SwapCallback(1, 0, abi.encode(WETH));
}
Four Production Boundaries for Gas Optimization
- Do not leave 1-wei dust for unknown contracts: dust holding is appropriate only for known tokens you repeatedly arbitrage, such as WETH, USDC, and AERO. Keeping 1 wei of every one-off low-quality meme token accumulates meaningless assets.
- Avoid
uint112reserve assumptions: upgraded Aerodrome or custom DEX implementations on Base may useuint256reserves. Readinguint256directly prevents numeric overflow. - Preserve atomic rollback: gas savings must not weaken safety. Enforce
require(netProfit >= minProfit)at the end of execution; insufficient profit or excess slippage must revert every pool change rather than complete a loss-making trade. - Use
safeTransferinstead of standard.transfer(): older or nonstandard tokens such as USDT do not return aboolafter successful transfer. Low-levelcallplus return-data length checks saves gas and is the only way to handle nonstandard ERC-20 behavior safely.
Enterprise DeFi and High-Frequency Trading Architecture Consulting

Fone works on low-level EVM architecture, market-maker liquidity management, low-latency arbitrage settlement engines, and full-stack white-label DEX delivery. Contact us if you are building a high-frequency trading bot, reducing contract execution gas, or auditing complex on-chain state machines:
- Explore our full-stack open-source solutions: White-Label DEX Launch Kit
- Read the complete arbitrage architecture: Production EVM Multihop Flash-Loan Arbitrage Architecture
- Follow the mathematical derivation: Closed-Form Multihop Arbitrage: Microsecond Virtual-Pool Folding
- Arrange a contract audit and gas assessment with a senior EVM architect: Discuss Custom Requirements with Fone