Development of Transaction Gas Optimization System
For protocols with constant transaction flow — automatic liquidations, rebalancing, keeper functions, bridge relayers — gas cost directly impacts economics. At $50 per liquidation with $30 gas cost, profitability critically depends on whether you send transaction at 20 Gwei or 80 Gwei. Gas optimization system is not a one-time smart contract refactor, but a complex of solutions at contract level, timing strategy, and infrastructure.
Gas Optimization at Smart Contract Level
First layer of optimization — contracts themselves. Here performance gain can be 30–70% compared to naive implementation.
Storage — Most Expensive Operation
SSTORE is one of most expensive opcodes. EIP-2929 (Istanbul) made first access to storage slot in transaction even more expensive (cold vs warm access). Strategies:
Packing storage variables — variables in one slot (32 bytes) are read and written together. Solidity compiler automatically packs variables smaller than 32 bytes if declared sequentially:
// BAD: 3 separate slots
uint256 amount;
address owner;
bool active;
// GOOD: 1 slot (address=20 bytes, bool=1 byte, remainder uint256 won't fit — already 2 slots)
address owner; // 20 bytes
bool active; // 1 byte
uint96 amount; // 12 bytes = total 33 bytes, 2 slots. Need to reconsider order:
// OPTIMAL:
bool active; // 1 byte
uint96 amount; // 12 bytes — total 13 bytes in first slot
address owner; // 20 bytes — also fits! Total 33 bytes — won't fit.
// Need to analyze each case via Foundry gas reports
Avoid repeated SLOAD in one function. Read variable to memory once, work with it:
// BAD: 3× SLOAD
if (balances[user] > 0) {
emit Withdrawal(balances[user]);
total -= balances[user];
balances[user] = 0;
}
// GOOD: 1× SLOAD
uint256 balance = balances[user];
if (balance > 0) {
emit Withdrawal(balance);
total -= balance;
balances[user] = 0;
}
Custom errors instead of require strings — error InsufficientBalance(uint256 available, uint256 required) vs require(balance >= amount, "Insufficient balance"). Custom error saves ~200 gas on deploy and ~50 gas on call.
Calldata Optimization
Zero bytes in calldata cost 4 gas, non-zero bytes — 16 gas (EIP-2028). When designing ABI functions this matters for operations with large calldata (batch functions, merkle proofs).
Bitmap for boolean flags instead of separate parameters:
// Instead of (bool useFlashLoan, bool reinvest, bool autoCompound) — 3 parameters
function execute(uint8 flags) external {
bool useFlashLoan = flags & 0x01 != 0;
bool reinvest = flags & 0x02 != 0;
bool autoCompound = flags & 0x04 != 0;
}
Multicall pattern — batching multiple calls in one transaction. Savings: 21000 gas (base transaction cost) × (N-1) for N operations. Implementation via OpenZeppelin Multicall or custom via delegatecall.
Yul/Assembly for Critical Paths
For inner loop functions with thousands of calls inline assembly gives 10–40% savings. Example — optimized token transfer:
function _efficientTransfer(address token, address to, uint256 amount) internal {
assembly {
let ptr := mload(0x40)
mstore(ptr, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(ptr, 0x04), and(to, 0xffffffffffffffffffffffffffffffffffffffff))
mstore(add(ptr, 0x24), amount)
if iszero(call(gas(), token, 0, ptr, 0x44, ptr, 0x20)) {
revert(0, 0)
}
}
}
Use only when profiling shows real bottleneck — Yul code is harder to audit.
Dynamic Transaction Timing Strategy
Second layer — when to send transaction, not just how to optimize it.
Gas Price Pattern Analysis
Gas in Ethereum has pronounced cyclical pattern: lowest on weekends (especially Sunday UTC 02:00–08:00), higher on weekdays during US trading session (14:00–22:00 UTC). For non-critical operations (treasury rebalancing, harvest yields), delaying to low-gas period saves real money.
EIP-1559 model: effectiveGasPrice = min(maxFeePerGas, baseFee + maxPriorityFeePerGas). baseFee algorithmically adapts to network load. Predictability of baseFee 1–3 blocks ahead is high — can wait for drop.
Gas Oracle: Decision-Making System
interface GasStrategy {
urgency: "immediate" | "fast" | "standard" | "slow";
maxBaseFee: bigint; // wait until baseFee falls below
maxPriorityFee: bigint;
deadline: number; // block after which send anyway
}
async function waitForOptimalGas(strategy: GasStrategy): Promise<void> {
while (true) {
const block = await provider.getBlock("latest");
const baseFee = block.baseFeePerGas!;
if (baseFee <= strategy.maxBaseFee ||
block.number >= strategy.deadline) {
break;
}
// Exponential backoff with jitter
const waitMs = Math.min(12000, 3000 * Math.random() + 3000);
await sleep(waitMs);
}
}
External data sources for gas prediction: Blocknative Gas Estimator API, Etherscan Gas Oracle, own model based on historical mempool data. For critical operations — multiple sources with aggregation.
Batch Operations and EIP-4337 Account Abstraction
Account Abstraction via EIP-4337 adds capabilities unavailable to EOA:
UserOperation batching: multiple operations in one UserOp with atomic execution. For keeper systems doing harvest + reinvest + rebalance — whole sequence in one transaction.
Gas sponsorship (Paymaster): protocol can pay for user gas. For automated systems useful differently — can pay gas in ERC-20 tokens instead of ETH if keeper wallet lacks ETH.
Parallel sending via EntryPoint: multiple independent UserOps from different users batched by bundler, base transaction cost savings divided proportionally.
Infrastructure Monitoring and Alerting
Gas optimization system without monitoring is a black box. Need metrics:
-
gas_price_paidvsgas_price_optimal(Prometheus gauge) -
transaction_pending_time— how long transaction hangs in mempool -
gas_savings_usd— estimated savings relative to naive strategy -
stuck_transactions_count— transactions not included for N blocks
Stuck transaction recovery: with sharp gas price spike transaction with low maxFeePerGas can hang for hours. Need watchdog service that after 10–20 blocks sends replacement transaction with same nonce and increased maxFeePerGas (+10% minimum, better +20%):
async function speedUpTransaction(originalTx: TransactionResponse) {
const currentBaseFee = (await provider.getBlock("latest"))!.baseFeePerGas!;
const newMaxFee = maxBigInt(
originalTx.maxFeePerGas! * 120n / 100n, // +20% from original
currentBaseFee * 2n // or 2× current baseFee
);
return wallet.sendTransaction({
...originalTx,
maxFeePerGas: newMaxFee,
maxPriorityFeePerGas: originalTx.maxPriorityFeePerGas! * 120n / 100n,
});
}
Overall effect from comprehensive gas optimization system for protocols with high transaction load: 40–70% reduction in gas costs relative to unoptimized baseline. Specific numbers depend on type of operations, network, and gas market volatility.







