[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-3-understanding-gas-costs":3},{"article":4,"author":54},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":7,"meta_description":16,"focus_keyword":17,"og_image":18,"canonical_url":18,"robots_meta":19,"created_at":15,"updated_at":15,"tags":20,"category_name":34,"related_articles":35},"d0000000-0000-0000-0000-000000000103","a0000000-0000-0000-0000-000000000002","Deep EVM #3: Understanding Gas — Why Your Contract Costs What It Costs","deep-evm-3-understanding-gas-costs","A precise breakdown of EVM gas costs: intrinsic gas, EIP-2929 cold\u002Fwarm access, refund mechanics, and proven optimization patterns that save real money.","## Gas Is Not an Abstraction\n\nDevelopers often treat gas as a vague \"cost\" metric. In reality, gas is a precisely defined resource accounting system with exact formulas. Every opcode, every byte of calldata, every storage access has a deterministic gas cost defined in the Yellow Paper and subsequent EIPs.\n\nUnderstanding these costs transforms gas optimization from guesswork into engineering.\n\n## Intrinsic Gas: The Baseline\n\nBefore your contract code executes a single opcode, the transaction has already consumed gas. This is the **intrinsic gas**:\n\n```\nintrinsic_gas = 21000                           \u002F\u002F base transaction cost\n             + 16 * nonzero_calldata_bytes       \u002F\u002F per nonzero byte\n             + 4 * zero_calldata_bytes            \u002F\u002F per zero byte\n             + access_list_cost                   \u002F\u002F if EIP-2930 access list\n             + 32000 (if contract creation)       \u002F\u002F CREATE transactions only\n```\n\nFor a simple ERC-20 `transfer(address,uint256)` call:\n- Base: 21000 gas\n- Function selector (4 bytes, likely nonzero): 4 * 16 = 64 gas\n- Address parameter (32 bytes, ~12 nonzero): 12 * 16 + 20 * 4 = 272 gas\n- Amount parameter (32 bytes, ~8 nonzero): 8 * 16 + 24 * 4 = 224 gas\n- **Total intrinsic: ~21,560 gas** — before any opcode executes.\n\nThis is why \"zero-calldata\" patterns matter for MEV bots. If your bot can encode information in zero bytes instead of nonzero bytes, you save 12 gas per byte. For a backrun transaction with 200 bytes of calldata, optimizing zero bytes can save 2,400 gas.\n\n## EIP-2929: The Access List Revolution\n\nBefore EIP-2929 (Berlin, April 2021), SLOAD always cost 800 gas and address access cost 700 gas. EIP-2929 changed everything by introducing per-transaction access sets.\n\n### How It Works\n\n1. At transaction start, only the sender and recipient addresses are in the \"warm\" set.\n2. The first time you touch an address (BALANCE, EXTCODESIZE, CALL, etc.) or storage slot (SLOAD, SSTORE), it is \"cold\" — you pay the cold surcharge.\n3. All subsequent accesses to the same address\u002Fslot within the transaction are \"warm\" — you pay the warm price.\n\n### Cold vs Warm Gas Costs\n\n| Opcode | Cold | Warm | Savings |\n|--------|------|------|--------|\n| SLOAD | 2100 | 100 | 2000 |\n| SSTORE | +2100 cold surcharge | base cost only | 2100 |\n| BALANCE | 2600 | 100 | 2500 |\n| EXTCODESIZE | 2600 | 100 | 2500 |\n| EXTCODECOPY | 2600 | 100 | 2500 |\n| EXTCODEHASH | 2600 | 100 | 2500 |\n| CALL | 2600 | 100 | 2500 |\n| STATICCALL | 2600 | 100 | 2500 |\n| DELEGATECALL | 2600 | 100 | 2500 |\n\n### EIP-2930 Access Lists\n\nYou can pre-declare which addresses and storage slots your transaction will access by including an **access list**. This \"pre-warms\" them, paying the cold cost upfront but at a discount:\n\n```\naccess_list_cost = 2400 per address + 1900 per storage key\n```\n\nCompare: cold SLOAD costs 2100 gas, but pre-warming via access list costs 1900. You save 200 gas per slot. For transactions that access many storage slots (like DEX swaps reading multiple pool variables), access lists provide meaningful savings.\n\n```json\n\u002F\u002F Transaction with access list\n{\n  \"to\": \"0xUniswapV3Pool\",\n  \"accessList\": [\n    {\n      \"address\": \"0xUniswapV3Pool\",\n      \"storageKeys\": [\n        \"0x0000...0000\",  \u002F\u002F slot0 (sqrtPriceX96, tick, etc.)\n        \"0x0000...0004\",  \u002F\u002F liquidity\n        \"0x0000...0003\"   \u002F\u002F feeGrowthGlobal\n      ]\n    }\n  ]\n}\n```\n\n## SSTORE: The Most Complex Opcode\n\nSSTORE gas cost is governed by EIP-2200 (Istanbul) and modified by EIP-2929 and EIP-3529. The cost depends on three factors: the original value (at transaction start), the current value, and the new value.\n\n### SSTORE Gas Table\n\n| Original | Current | New | Gas Cost | Refund |\n|----------|---------|-----|----------|--------|\n| 0 | 0 | nonzero | 22100 (cold) \u002F 20000 (warm) | 0 |\n| nonzero | same | different nonzero | 5000 (cold) \u002F 2900 (warm) | 0 |\n| nonzero | same | 0 | 5000 (cold) \u002F 2900 (warm) | 4800 |\n| nonzero | different | original | 200 | restore refund* |\n| 0 | nonzero | 0 | 200 | 19900 |\n\n*Restore refunds vary based on whether the slot was originally zero or nonzero.\n\nThe key insight: **setting a zero slot to nonzero costs 20000 gas** (writing a new state trie leaf), while **modifying an existing nonzero slot costs 2900 gas** (warm). This 7x difference is why:\n\n- Initializing mappings for new users is expensive (first transfer to a new address)\n- Using `1` instead of `0` as the \"unset\" value for reentrancy locks saves 17100 gas\n- Deleting storage (setting to 0) gives a refund, but not enough to make \"create then delete\" patterns free\n\n## Gas Refunds After EIP-3529\n\nEIP-3529 (London, August 2021) dramatically reduced gas refunds:\n\n- Maximum refund capped at **20% of total gas used** (was 50%)\n- SELFDESTRUCT refund removed entirely\n- Only SSTORE nonzero-to-zero refund remains: 4800 gas\n\nThis killed gas tokens (CHI, GST2) which exploited the old 50% refund by creating and destroying contracts\u002Fstorage to \"bank\" gas refunds. With a 20% cap, the math no longer works.\n\n## Optimization Patterns That Actually Matter\n\n### 1. Cache Storage Reads in Memory\n\nEvery SLOAD costs 100-2100 gas. If you read the same slot multiple times, cache it:\n\n```solidity\n\u002F\u002F BAD: 3 SLOADs (300+ gas)\nfunction withdraw() external {\n    require(balances[msg.sender] > 0);\n    uint256 amount = balances[msg.sender];\n    balances[msg.sender] = 0;\n}\n\n\u002F\u002F GOOD: 1 SLOAD + 1 SSTORE (but Solidity often optimizes this)\nfunction withdraw() external {\n    uint256 amount = balances[msg.sender];\n    require(amount > 0);\n    balances[msg.sender] = 0;\n}\n```\n\n### 2. Pack Storage Variables\n\nMultiple variables in one slot = one SLOAD instead of many:\n\n```solidity\n\u002F\u002F 3 slots, 3 SLOADs = 6300 gas (cold)\nuint256 price;     \u002F\u002F slot 0\nuint256 amount;    \u002F\u002F slot 1\nuint256 timestamp; \u002F\u002F slot 2\n\n\u002F\u002F 1 slot, 1 SLOAD = 2100 gas (cold)\nuint128 price;     \u002F\u002F slot 0, lower 128 bits\nuint64 amount;     \u002F\u002F slot 0, next 64 bits\nuint64 timestamp;  \u002F\u002F slot 0, upper 64 bits\n```\n\n### 3. Use Calldata Instead of Memory for External Parameters\n\n```solidity\n\u002F\u002F BAD: copies array to memory\nfunction processOrders(Order[] memory orders) external { ... }\n\n\u002F\u002F GOOD: reads directly from calldata\nfunction processOrders(Order[] calldata orders) external { ... }\n```\n\n### 4. Short-Circuit Expensive Operations\n\n```solidity\n\u002F\u002F BAD: always reads storage\nfunction isAllowed(address user) public view returns (bool) {\n    return allowlist[user] || owner() == user;\n}\n\n\u002F\u002F GOOD: check cheap comparison first\nfunction isAllowed(address user) public view returns (bool) {\n    if (owner() == user) return true;  \u002F\u002F CALLER is 2 gas\n    return allowlist[user];             \u002F\u002F SLOAD only if needed\n}\n```\n\n### 5. Use Unchecked Arithmetic When Safe\n\n```solidity\n\u002F\u002F Solidity 0.8+ adds overflow checks: ~40 gas overhead per operation\nfor (uint256 i = 0; i \u003C length; i++) { ... }\n\n\u002F\u002F Unchecked: saves ~40 gas per iteration\nfor (uint256 i = 0; i \u003C length;) {\n    \u002F\u002F ... loop body ...\n    unchecked { ++i; }\n}\n```\n\n### 6. Use Custom Errors Instead of Require Strings\n\n```solidity\n\u002F\u002F BAD: stores string in bytecode, expensive revert data\nrequire(amount > 0, \"Amount must be positive\");\n\n\u002F\u002F GOOD: 4-byte selector, no string storage\nerror InvalidAmount();\nif (amount == 0) revert InvalidAmount();\n```\n\nCustom errors save both deployment gas (less bytecode) and runtime gas (4 bytes vs 64+ bytes of revert data).\n\n### 7. Minimize Calldata Zeros vs Nonzeros\n\nFor MEV bots, this is critical. Structure your calldata to maximize zero bytes:\n\n```\n\u002F\u002F 16 gas per nonzero byte vs 4 gas per zero byte\n\u002F\u002F If you can choose how to encode your data, prefer representations\n\u002F\u002F that contain more zero bytes.\n\u002F\u002F Example: encode amounts in wei rather than custom units when\n\u002F\u002F the wei representation has trailing zeros.\n```\n\n## Real-World Gas Breakdown: Uniswap V3 Swap\n\nLet us trace the gas consumption of a typical Uniswap V3 exact-input swap:\n\n| Component | Gas |\n|-----------|-----|\n| Intrinsic (21000 + calldata) | ~21,500 |\n| Function dispatch | ~100 |\n| Input validation | ~200 |\n| Pool state reads (slot0, liquidity) | ~4,200 (cold) |\n| Price computation (tick math) | ~5,000 |\n| Balance updates (2 SSTOREs) | ~10,000 |\n| Token transfers (2 external calls) | ~15,000 |\n| Event emission | ~1,500 |\n| **Total** | **~57,500** |\n\nNearly 60% of the gas goes to storage operations and external calls. The actual math (price computation, tick crossing) is a small fraction.\n\n## Conclusion\n\nGas optimization is not about micro-optimizing arithmetic opcodes — it is about minimizing storage access and external calls. The patterns that save the most gas are architectural: storage packing, caching, calldata usage, and access list optimization. In the next article, we turn to security: how the EVM's execution model creates — and prevents — vulnerabilities like reentrancy.","\u003Ch2 id=\"gas-is-not-an-abstraction\">Gas Is Not an Abstraction\u003C\u002Fh2>\n\u003Cp>Developers often treat gas as a vague “cost” metric. In reality, gas is a precisely defined resource accounting system with exact formulas. Every opcode, every byte of calldata, every storage access has a deterministic gas cost defined in the Yellow Paper and subsequent EIPs.\u003C\u002Fp>\n\u003Cp>Understanding these costs transforms gas optimization from guesswork into engineering.\u003C\u002Fp>\n\u003Ch2 id=\"intrinsic-gas-the-baseline\">Intrinsic Gas: The Baseline\u003C\u002Fh2>\n\u003Cp>Before your contract code executes a single opcode, the transaction has already consumed gas. This is the \u003Cstrong>intrinsic gas\u003C\u002Fstrong>:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>intrinsic_gas = 21000                           \u002F\u002F base transaction cost\n             + 16 * nonzero_calldata_bytes       \u002F\u002F per nonzero byte\n             + 4 * zero_calldata_bytes            \u002F\u002F per zero byte\n             + access_list_cost                   \u002F\u002F if EIP-2930 access list\n             + 32000 (if contract creation)       \u002F\u002F CREATE transactions only\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>For a simple ERC-20 \u003Ccode>transfer(address,uint256)\u003C\u002Fcode> call:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Base: 21000 gas\u003C\u002Fli>\n\u003Cli>Function selector (4 bytes, likely nonzero): 4 * 16 = 64 gas\u003C\u002Fli>\n\u003Cli>Address parameter (32 bytes, ~12 nonzero): 12 * 16 + 20 * 4 = 272 gas\u003C\u002Fli>\n\u003Cli>Amount parameter (32 bytes, ~8 nonzero): 8 * 16 + 24 * 4 = 224 gas\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Total intrinsic: ~21,560 gas\u003C\u002Fstrong> — before any opcode executes.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>This is why “zero-calldata” patterns matter for MEV bots. If your bot can encode information in zero bytes instead of nonzero bytes, you save 12 gas per byte. For a backrun transaction with 200 bytes of calldata, optimizing zero bytes can save 2,400 gas.\u003C\u002Fp>\n\u003Ch2 id=\"eip-2929-the-access-list-revolution\">EIP-2929: The Access List Revolution\u003C\u002Fh2>\n\u003Cp>Before EIP-2929 (Berlin, April 2021), SLOAD always cost 800 gas and address access cost 700 gas. EIP-2929 changed everything by introducing per-transaction access sets.\u003C\u002Fp>\n\u003Ch3>How It Works\u003C\u002Fh3>\n\u003Col>\n\u003Cli>At transaction start, only the sender and recipient addresses are in the “warm” set.\u003C\u002Fli>\n\u003Cli>The first time you touch an address (BALANCE, EXTCODESIZE, CALL, etc.) or storage slot (SLOAD, SSTORE), it is “cold” — you pay the cold surcharge.\u003C\u002Fli>\n\u003Cli>All subsequent accesses to the same address\u002Fslot within the transaction are “warm” — you pay the warm price.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>Cold vs Warm Gas Costs\u003C\u002Fh3>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Opcode\u003C\u002Fth>\u003Cth>Cold\u003C\u002Fth>\u003Cth>Warm\u003C\u002Fth>\u003Cth>Savings\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>SLOAD\u003C\u002Ftd>\u003Ctd>2100\u003C\u002Ftd>\u003Ctd>100\u003C\u002Ftd>\u003Ctd>2000\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>SSTORE\u003C\u002Ftd>\u003Ctd>+2100 cold surcharge\u003C\u002Ftd>\u003Ctd>base cost only\u003C\u002Ftd>\u003Ctd>2100\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>BALANCE\u003C\u002Ftd>\u003Ctd>2600\u003C\u002Ftd>\u003Ctd>100\u003C\u002Ftd>\u003Ctd>2500\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>EXTCODESIZE\u003C\u002Ftd>\u003Ctd>2600\u003C\u002Ftd>\u003Ctd>100\u003C\u002Ftd>\u003Ctd>2500\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>EXTCODECOPY\u003C\u002Ftd>\u003Ctd>2600\u003C\u002Ftd>\u003Ctd>100\u003C\u002Ftd>\u003Ctd>2500\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>EXTCODEHASH\u003C\u002Ftd>\u003Ctd>2600\u003C\u002Ftd>\u003Ctd>100\u003C\u002Ftd>\u003Ctd>2500\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>CALL\u003C\u002Ftd>\u003Ctd>2600\u003C\u002Ftd>\u003Ctd>100\u003C\u002Ftd>\u003Ctd>2500\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>STATICCALL\u003C\u002Ftd>\u003Ctd>2600\u003C\u002Ftd>\u003Ctd>100\u003C\u002Ftd>\u003Ctd>2500\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>DELEGATECALL\u003C\u002Ftd>\u003Ctd>2600\u003C\u002Ftd>\u003Ctd>100\u003C\u002Ftd>\u003Ctd>2500\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Ch3>EIP-2930 Access Lists\u003C\u002Fh3>\n\u003Cp>You can pre-declare which addresses and storage slots your transaction will access by including an \u003Cstrong>access list\u003C\u002Fstrong>. This “pre-warms” them, paying the cold cost upfront but at a discount:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>access_list_cost = 2400 per address + 1900 per storage key\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Compare: cold SLOAD costs 2100 gas, but pre-warming via access list costs 1900. You save 200 gas per slot. For transactions that access many storage slots (like DEX swaps reading multiple pool variables), access lists provide meaningful savings.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">\u002F\u002F Transaction with access list\n{\n  \"to\": \"0xUniswapV3Pool\",\n  \"accessList\": [\n    {\n      \"address\": \"0xUniswapV3Pool\",\n      \"storageKeys\": [\n        \"0x0000...0000\",  \u002F\u002F slot0 (sqrtPriceX96, tick, etc.)\n        \"0x0000...0004\",  \u002F\u002F liquidity\n        \"0x0000...0003\"   \u002F\u002F feeGrowthGlobal\n      ]\n    }\n  ]\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"sstore-the-most-complex-opcode\">SSTORE: The Most Complex Opcode\u003C\u002Fh2>\n\u003Cp>SSTORE gas cost is governed by EIP-2200 (Istanbul) and modified by EIP-2929 and EIP-3529. The cost depends on three factors: the original value (at transaction start), the current value, and the new value.\u003C\u002Fp>\n\u003Ch3>SSTORE Gas Table\u003C\u002Fh3>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Original\u003C\u002Fth>\u003Cth>Current\u003C\u002Fth>\u003Cth>New\u003C\u002Fth>\u003Cth>Gas Cost\u003C\u002Fth>\u003Cth>Refund\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>0\u003C\u002Ftd>\u003Ctd>0\u003C\u002Ftd>\u003Ctd>nonzero\u003C\u002Ftd>\u003Ctd>22100 (cold) \u002F 20000 (warm)\u003C\u002Ftd>\u003Ctd>0\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>nonzero\u003C\u002Ftd>\u003Ctd>same\u003C\u002Ftd>\u003Ctd>different nonzero\u003C\u002Ftd>\u003Ctd>5000 (cold) \u002F 2900 (warm)\u003C\u002Ftd>\u003Ctd>0\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>nonzero\u003C\u002Ftd>\u003Ctd>same\u003C\u002Ftd>\u003Ctd>0\u003C\u002Ftd>\u003Ctd>5000 (cold) \u002F 2900 (warm)\u003C\u002Ftd>\u003Ctd>4800\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>nonzero\u003C\u002Ftd>\u003Ctd>different\u003C\u002Ftd>\u003Ctd>original\u003C\u002Ftd>\u003Ctd>200\u003C\u002Ftd>\u003Ctd>restore refund*\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>0\u003C\u002Ftd>\u003Ctd>nonzero\u003C\u002Ftd>\u003Ctd>0\u003C\u002Ftd>\u003Ctd>200\u003C\u002Ftd>\u003Ctd>19900\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Cp>*Restore refunds vary based on whether the slot was originally zero or nonzero.\u003C\u002Fp>\n\u003Cp>The key insight: \u003Cstrong>setting a zero slot to nonzero costs 20000 gas\u003C\u002Fstrong> (writing a new state trie leaf), while \u003Cstrong>modifying an existing nonzero slot costs 2900 gas\u003C\u002Fstrong> (warm). This 7x difference is why:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Initializing mappings for new users is expensive (first transfer to a new address)\u003C\u002Fli>\n\u003Cli>Using \u003Ccode>1\u003C\u002Fcode> instead of \u003Ccode>0\u003C\u002Fcode> as the “unset” value for reentrancy locks saves 17100 gas\u003C\u002Fli>\n\u003Cli>Deleting storage (setting to 0) gives a refund, but not enough to make “create then delete” patterns free\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"gas-refunds-after-eip-3529\">Gas Refunds After EIP-3529\u003C\u002Fh2>\n\u003Cp>EIP-3529 (London, August 2021) dramatically reduced gas refunds:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Maximum refund capped at \u003Cstrong>20% of total gas used\u003C\u002Fstrong> (was 50%)\u003C\u002Fli>\n\u003Cli>SELFDESTRUCT refund removed entirely\u003C\u002Fli>\n\u003Cli>Only SSTORE nonzero-to-zero refund remains: 4800 gas\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>This killed gas tokens (CHI, GST2) which exploited the old 50% refund by creating and destroying contracts\u002Fstorage to “bank” gas refunds. With a 20% cap, the math no longer works.\u003C\u002Fp>\n\u003Ch2 id=\"optimization-patterns-that-actually-matter\">Optimization Patterns That Actually Matter\u003C\u002Fh2>\n\u003Ch3>1. Cache Storage Reads in Memory\u003C\u002Fh3>\n\u003Cp>Every SLOAD costs 100-2100 gas. If you read the same slot multiple times, cache it:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F BAD: 3 SLOADs (300+ gas)\nfunction withdraw() external {\n    require(balances[msg.sender] &gt; 0);\n    uint256 amount = balances[msg.sender];\n    balances[msg.sender] = 0;\n}\n\n\u002F\u002F GOOD: 1 SLOAD + 1 SSTORE (but Solidity often optimizes this)\nfunction withdraw() external {\n    uint256 amount = balances[msg.sender];\n    require(amount &gt; 0);\n    balances[msg.sender] = 0;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>2. Pack Storage Variables\u003C\u002Fh3>\n\u003Cp>Multiple variables in one slot = one SLOAD instead of many:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F 3 slots, 3 SLOADs = 6300 gas (cold)\nuint256 price;     \u002F\u002F slot 0\nuint256 amount;    \u002F\u002F slot 1\nuint256 timestamp; \u002F\u002F slot 2\n\n\u002F\u002F 1 slot, 1 SLOAD = 2100 gas (cold)\nuint128 price;     \u002F\u002F slot 0, lower 128 bits\nuint64 amount;     \u002F\u002F slot 0, next 64 bits\nuint64 timestamp;  \u002F\u002F slot 0, upper 64 bits\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>3. Use Calldata Instead of Memory for External Parameters\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F BAD: copies array to memory\nfunction processOrders(Order[] memory orders) external { ... }\n\n\u002F\u002F GOOD: reads directly from calldata\nfunction processOrders(Order[] calldata orders) external { ... }\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>4. Short-Circuit Expensive Operations\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F BAD: always reads storage\nfunction isAllowed(address user) public view returns (bool) {\n    return allowlist[user] || owner() == user;\n}\n\n\u002F\u002F GOOD: check cheap comparison first\nfunction isAllowed(address user) public view returns (bool) {\n    if (owner() == user) return true;  \u002F\u002F CALLER is 2 gas\n    return allowlist[user];             \u002F\u002F SLOAD only if needed\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>5. Use Unchecked Arithmetic When Safe\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F Solidity 0.8+ adds overflow checks: ~40 gas overhead per operation\nfor (uint256 i = 0; i &lt; length; i++) { ... }\n\n\u002F\u002F Unchecked: saves ~40 gas per iteration\nfor (uint256 i = 0; i &lt; length;) {\n    \u002F\u002F ... loop body ...\n    unchecked { ++i; }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>6. Use Custom Errors Instead of Require Strings\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F BAD: stores string in bytecode, expensive revert data\nrequire(amount &gt; 0, \"Amount must be positive\");\n\n\u002F\u002F GOOD: 4-byte selector, no string storage\nerror InvalidAmount();\nif (amount == 0) revert InvalidAmount();\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Custom errors save both deployment gas (less bytecode) and runtime gas (4 bytes vs 64+ bytes of revert data).\u003C\u002Fp>\n\u003Ch3>7. Minimize Calldata Zeros vs Nonzeros\u003C\u002Fh3>\n\u003Cp>For MEV bots, this is critical. Structure your calldata to maximize zero bytes:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002F\u002F 16 gas per nonzero byte vs 4 gas per zero byte\n\u002F\u002F If you can choose how to encode your data, prefer representations\n\u002F\u002F that contain more zero bytes.\n\u002F\u002F Example: encode amounts in wei rather than custom units when\n\u002F\u002F the wei representation has trailing zeros.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"real-world-gas-breakdown-uniswap-v3-swap\">Real-World Gas Breakdown: Uniswap V3 Swap\u003C\u002Fh2>\n\u003Cp>Let us trace the gas consumption of a typical Uniswap V3 exact-input swap:\u003C\u002Fp>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Component\u003C\u002Fth>\u003Cth>Gas\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>Intrinsic (21000 + calldata)\u003C\u002Ftd>\u003Ctd>~21,500\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Function dispatch\u003C\u002Ftd>\u003Ctd>~100\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Input validation\u003C\u002Ftd>\u003Ctd>~200\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Pool state reads (slot0, liquidity)\u003C\u002Ftd>\u003Ctd>~4,200 (cold)\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Price computation (tick math)\u003C\u002Ftd>\u003Ctd>~5,000\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Balance updates (2 SSTOREs)\u003C\u002Ftd>\u003Ctd>~10,000\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Token transfers (2 external calls)\u003C\u002Ftd>\u003Ctd>~15,000\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Event emission\u003C\u002Ftd>\u003Ctd>~1,500\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>\u003Cstrong>Total\u003C\u002Fstrong>\u003C\u002Ftd>\u003Ctd>\u003Cstrong>~57,500\u003C\u002Fstrong>\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Cp>Nearly 60% of the gas goes to storage operations and external calls. The actual math (price computation, tick crossing) is a small fraction.\u003C\u002Fp>\n\u003Ch2 id=\"conclusion\">Conclusion\u003C\u002Fh2>\n\u003Cp>Gas optimization is not about micro-optimizing arithmetic opcodes — it is about minimizing storage access and external calls. The patterns that save the most gas are architectural: storage packing, caching, calldata usage, and access list optimization. In the next article, we turn to security: how the EVM’s execution model creates — and prevents — vulnerabilities like reentrancy.\u003C\u002Fp>\n","en","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:22.667347Z","Precise EVM gas breakdown: intrinsic gas, EIP-2929 cold\u002Fwarm access, SSTORE costs, refund mechanics, and proven optimization patterns.","EVM gas optimization",null,"index, follow",[21,26,30],{"id":22,"name":23,"slug":24,"created_at":25},"c0000000-0000-0000-0000-000000000016","EVM","evm","2026-03-28T10:44:21.513630Z",{"id":27,"name":28,"slug":29,"created_at":25},"c0000000-0000-0000-0000-000000000020","Gas Optimization","gas-optimization",{"id":31,"name":32,"slug":33,"created_at":25},"c0000000-0000-0000-0000-000000000014","Solidity","solidity","Blockchain",[36,42,48],{"id":37,"title":38,"slug":39,"excerpt":40,"locale":12,"category_name":34,"published_at":41},"de000000-0000-0000-0000-000000000003","The Ethereum Interoperability Layer: How 55+ L2s Become One Chain","ethereum-interoperability-layer-how-55-l2s-become-one-chain","Ethereum has 55+ Layer 2 rollups, fragmenting liquidity and user experience. The Ethereum Interoperability Layer — combining cross-rollup messaging, shared sequencers, and based rollups — aims to unify them into a single composable network.","2026-03-28T10:44:35.632478Z",{"id":43,"title":44,"slug":45,"excerpt":46,"locale":12,"category_name":34,"published_at":47},"de000000-0000-0000-0000-000000000002","ZK Proofs Beyond Rollups: Verifiable AI Inference on Ethereum","zk-proofs-beyond-rollups-verifiable-ai-inference-ethereum","Zero-knowledge proofs are no longer just a scaling tool. In 2026, zkML enables verifiable AI inference on-chain, ZK coprocessors move heavy computation off-chain with on-chain verification, and new proving systems like SP1 and Jolt make it practical.","2026-03-28T10:44:35.618408Z",{"id":49,"title":50,"slug":51,"excerpt":52,"locale":12,"category_name":34,"published_at":53},"dd000000-0000-0000-0000-000000000003","EIP-7702 in Practice: Building Smart Account Flows After Pectra","eip-7702-in-practice-building-smart-account-flows-after-pectra","EIP-7702 lets any Ethereum EOA temporarily act as a smart contract within a single transaction. Here is how to implement batch transactions, gas sponsorship, and social recovery using the new account abstraction primitive.","2026-03-28T10:44:35.031290Z",{"id":13,"name":55,"slug":56,"bio":57,"photo_url":18,"linkedin":18,"role":58,"created_at":59,"updated_at":59},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]