This document describes the gas optimization techniques applied to AgenticPay EVM smart contracts. All optimizations are designed to maintain functional equivalence while reducing gas costs.
Assembly is used in performance-critical operations where Solidity generates suboptimal EVM bytecode:
- SLOAD/SSTORE optimizations: Direct storage slot access via
sload/sstoreavoids Solidity's bounds checking and automatic retry logic - Self-balance:
selfbalance()replacesaddress(this).balance(saves ~20 gas per call) - Mappings: Direct slot computation for mapping reads avoids redundant keccak256 calculations
Example - SplitterV1:
uint256 len;
assembly {
len := sload(recipients.slot)
}Safe arithmetic (Solidity 0.8+ default) is bypassed where overflow is provably impossible:
- Loop counters (
++i) - Timestamp calculations (
block.timestamp + delay) - Balance subtractions (checked earlier via
ifguards) - Fee calculations bounded by basis-point constraints
All string-based require statements replaced with custom errors:
- Before:
require(ok, "Transfer failed"); - After:
revert TransferFailed(to, amount); - Savings: ~50 gas per occurrence (shorter deploy bytecode + cheaper reverts)
State variables arranged to minimize slot usage:
uint16for basis points (never exceeds 10,000)boolfor flags (packed with adjacent variables)uint256for timestamps (avoids unnecessary casting)
Using storage pointers instead of memory copies to avoid copying entire structs:
- Before:
Recipient memory r = recipients[i]; - After:
Recipient storage r = recipients[i];
Storage reads are cached in local variables when the value doesn't change:
uint16 _platformFeeBps;
assembly {
_platformFeeBps := sload(platformFeeBps.slot)
}- Pre-compute array lengths
- Use
unchecked { ++i; }pattern for iteration - Use
storagereferences to avoid copying
| Contract | Key Optimizations | Estimated Savings |
|---|---|---|
| SplitterV1 | Assembly SLOAD, storage pointers, unchecked math | ~15-20% |
| TokenizedFiat | Custom errors, unchecked math, storage caching | ~10-15% |
| TimelockController | Assembly mapping, unchecked math, require→custom errors | ~10-15% |
| EmergencyPause | Assembly mapping, storage optimizations | ~10-15% |
| BridgeHTLC | Custom errors, storage pointers, unchecked math | ~10-15% |
| RelayPaymaster | Custom errors, unchecked math | ~10-15% |
| GasPriceOracle | Assembly mapping reads, unchecked math | ~15-20% |
cd contracts/evm
REPORT_GAS=true npm run test:gasTo run specific gas benchmark tests:
npx hardhat test test/gas/GasBenchmark.test.tsThe CI pipeline includes a gas regression check that fails if gas increases beyond a threshold. See .github/workflows/contracts-evm.yml for configuration.