[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-1-how-evm-executes-code-opcodes-stack-gas":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-000000000101","a0000000-0000-0000-0000-000000000002","Deep EVM #1: How the EVM Executes Your Code — Opcodes, Stack, and Gas","deep-evm-1-how-evm-executes-code-opcodes-stack-gas","A low-level walkthrough of the Ethereum Virtual Machine: how opcodes manipulate the stack, how gas metering works, and what actually happens when your transaction executes.","## The EVM Is a Stack Machine\n\nThe Ethereum Virtual Machine is not like the x86 processor in your laptop. It has no registers. Instead, it is a **stack machine** — every computation pushes to or pops from a 1024-element stack where each element is a 256-bit (32-byte) word.\n\nWhen you call a smart contract, the EVM receives the contract's bytecode — a flat sequence of single-byte opcodes — and begins executing from byte 0. There is no function table, no ELF header, no linking step. The bytecode is the program.\n\n```\n\u002F\u002F Solidity:\n\u002F\u002F uint256 result = 2 + 3;\n\n\u002F\u002F Compiles to bytecode:\n\u002F\u002F PUSH1 0x02  PUSH1 0x03  ADD\n\n\u002F\u002F Stack trace:\n\u002F\u002F []           -> PUSH1 0x02 -> [2]\n\u002F\u002F [2]          -> PUSH1 0x03 -> [2, 3]\n\u002F\u002F [2, 3]       -> ADD        -> [5]\n```\n\nEvery opcode consumes its operands from the top of the stack and pushes its result back. The ADD opcode pops two values, adds them, and pushes the sum. This is fundamentally different from register-based architectures where you specify source and destination registers.\n\n## Opcode Categories\n\nThe EVM defines roughly 140 opcodes, grouped into functional categories:\n\n### Arithmetic and Comparison\n- **ADD, SUB, MUL, DIV, MOD** — Basic 256-bit integer arithmetic. All cost 3 gas (the G_verylow tier).\n- **SDIV, SMOD** — Signed division and modulo using two's complement.\n- **ADDMOD, MULMOD** — Modular arithmetic: `(a + b) % N` and `(a * b) % N` in a single opcode. These are critical for elliptic curve operations and cost 8 gas.\n- **EXP** — Exponentiation. Costs 10 gas + 50 per byte in the exponent, making it one of the more expensive arithmetic opcodes.\n- **LT, GT, SLT, SGT, EQ, ISZERO** — Comparison opcodes that push 1 (true) or 0 (false).\n\n### Bitwise Operations\n- **AND, OR, XOR, NOT** — Bitwise logic, 3 gas each.\n- **SHL, SHR, SAR** — Shift left, logical shift right, arithmetic shift right (added in Constantinople, EIP-145). Before these existed, shifts required MUL\u002FDIV by powers of 2.\n- **BYTE** — Extract a single byte from a 32-byte word. `BYTE(0, x)` returns the most significant byte.\n\n### Stack Manipulation\n- **POP** — Discard the top element.\n- **PUSH1 through PUSH32** — Push 1 to 32 bytes of immediate data onto the stack. PUSH1 is the most common opcode in deployed bytecode.\n- **DUP1 through DUP16** — Duplicate the Nth stack element to the top.\n- **SWAP1 through SWAP16** — Swap the top element with the Nth element below it.\n\n### Environment and Block Information\n- **CALLER** (msg.sender), **CALLVALUE** (msg.value), **CALLDATALOAD**, **CALLDATASIZE**, **CALLDATACOPY** — Access transaction context.\n- **NUMBER**, **TIMESTAMP**, **BASEFEE**, **CHAINID** — Block-level information.\n- **BALANCE**, **EXTCODESIZE**, **EXTCODECOPY** — Query other accounts.\n\n## The Gas Schedule\n\nEvery opcode has a gas cost. Gas serves two purposes: it prevents infinite loops (the halting problem) and it prices computational resources fairly.\n\nThe gas costs fall into tiers:\n\n| Tier | Gas | Examples |\n|------|-----|----------|\n| Zero | 0 | STOP, RETURN, REVERT |\n| Base | 2 | ADDRESS, ORIGIN, CALLER |\n| Very Low | 3 | ADD, SUB, LT, GT, AND, OR, POP |\n| Low | 5 | MUL, DIV, MOD |\n| Mid | 8 | ADDMOD, MULMOD, JUMP |\n| High | 10 | JUMPI |\n| Special | varies | SLOAD, SSTORE, CALL, CREATE |\n\nThe expensive opcodes are the ones that touch state:\n\n```\n\u002F\u002F Gas costs for state access (post EIP-2929):\n\u002F\u002F SLOAD (cold):   2100 gas\n\u002F\u002F SLOAD (warm):    100 gas\n\u002F\u002F SSTORE (cold, 0->nonzero): 22100 gas\n\u002F\u002F SSTORE (warm):   100 gas (+ 20000 if 0->nonzero)\n\u002F\u002F CALL (cold):    2600 gas\n\u002F\u002F CALL (warm):     100 gas\n\u002F\u002F BALANCE (cold): 2600 gas\n\u002F\u002F BALANCE (warm):  100 gas\n```\n\n## Cold vs Warm Access (EIP-2929)\n\nEIP-2929 (Berlin upgrade, April 2021) introduced the concept of an **access list** — a per-transaction set of addresses and storage slots that have been touched.\n\nThe first time you access a storage slot or external address within a transaction, it is \"cold\" and costs extra gas. Subsequent accesses are \"warm\" and cheap. This is why the order you read storage slots matters for gas optimization.\n\n```solidity\n\u002F\u002F In Solidity, this pattern is expensive:\nfunction bad() external view returns (uint256) {\n    \u002F\u002F First read of slot: 2100 gas (cold)\n    uint256 a = myStorage;\n    \u002F\u002F ... some logic ...\n    \u002F\u002F Second read: 100 gas (warm) -- compiler may or may not cache this\n    uint256 b = myStorage;\n    return a + b;\n}\n\n\u002F\u002F Cache in memory:\nfunction good() external view returns (uint256) {\n    uint256 cached = myStorage; \u002F\u002F 2100 gas (cold), only once\n    return cached + cached;     \u002F\u002F 6 gas (ADD + DUP)\n}\n```\n\n## Execution Flow: What Happens in a Transaction\n\nWhen you send a transaction that calls a contract, here is the full execution sequence:\n\n1. **Transaction validation** — Nonce check, balance >= value + gas * gasPrice, signature verification.\n2. **Intrinsic gas deduction** — 21000 gas for the transaction itself, plus 16 gas per non-zero calldata byte and 4 per zero byte.\n3. **Context setup** — The EVM creates an execution context: code, calldata, caller, value, gas remaining.\n4. **Program counter starts at 0** — The EVM reads the opcode at position 0 and executes it.\n5. **Sequential execution** — Each opcode is executed, gas is deducted. JUMP and JUMPI allow non-linear control flow, but only to positions marked with JUMPDEST.\n6. **Termination** — Execution ends with STOP (success, no return data), RETURN (success, with return data), REVERT (failure, state reverted, return error data), or running out of gas.\n7. **State commit or rollback** — On success, all state changes are committed. On revert, all changes within this call context are rolled back.\n\n## The Program Counter and JUMP\n\nThe program counter (PC) is an implicit register that tracks the current position in the bytecode. Most opcodes advance the PC by 1 (or by 1 + N for PUSH opcodes). Two opcodes modify the PC directly:\n\n- **JUMP** — Pops a destination from the stack, sets PC to that value. The destination must contain a JUMPDEST opcode or the transaction reverts.\n- **JUMPI** — Conditional jump. Pops destination and condition. If condition is nonzero, jump; otherwise continue sequentially.\n\nThis is how the EVM implements if\u002Felse, loops, and function dispatch. The Solidity compiler generates a function selector that loads the first 4 bytes of calldata, compares against known function signatures, and JUMPIs to the matching code block.\n\n```\n\u002F\u002F Function dispatch (simplified bytecode):\n\u002F\u002F CALLDATALOAD(0) -> SHR(224) -> function_selector\n\u002F\u002F DUP1 PUSH4 0xa9059cbb EQ PUSH2 0x00a4 JUMPI  \u002F\u002F transfer(address,uint256)\n\u002F\u002F DUP1 PUSH4 0x70a08231 EQ PUSH2 0x00d2 JUMPI  \u002F\u002F balanceOf(address)\n\u002F\u002F PUSH1 0x00 DUP1 REVERT                         \u002F\u002F fallback: revert\n```\n\n## Sub-calls: CALL, STATICCALL, DELEGATECALL\n\nContracts can invoke other contracts using three call opcodes:\n\n- **CALL** — Standard call. Creates a new execution context with its own stack and memory. The callee runs independently; if it reverts, only its changes are rolled back.\n- **STATICCALL** — Read-only call (EIP-214). Any state-modifying opcode (SSTORE, CREATE, LOG, SELFDESTRUCT) within the callee causes an immediate revert.\n- **DELEGATECALL** — Executes the callee's code but in the caller's storage context. msg.sender and msg.value are preserved from the original call. This is how proxy patterns and libraries work.\n\nEach call opcode takes 7 stack arguments: gas, address, value (except STATICCALL\u002FDELEGATECALL), argsOffset, argsLength, retOffset, retLength. The gas cost is 100 (warm) or 2600 (cold) plus any value transfer surcharge.\n\n## Gas Refunds and Their Limits\n\nHistorically, clearing a storage slot from nonzero to zero gave a gas refund — incentivizing state cleanup. Post EIP-3529 (London upgrade), the maximum refund is capped at 20% of total gas used (down from 50%). This killed the gas token arbitrage (CHI, GST2) that exploited refunds for profit.\n\nThe remaining refund sources:\n- SSTORE: setting a nonzero slot back to zero refunds 4800 gas.\n- SELFDESTRUCT: removed as a refund source in EIP-3529.\n\n## Practical Implications for MEV\n\nIf you are building MEV bots, understanding the EVM at the opcode level is not optional — it is a competitive requirement. Every gas unit saved in your bot's execution is profit margin. Key insights:\n\n- **Simulate before submitting** — Use `eth_call` or a local EVM (revm, EVMONE) to trace execution and know the exact gas cost before your bundle hits the builder.\n- **Minimize cold access** — Pre-warm storage slots via access lists (EIP-2930).\n- **Use STATICCALL for reads** — Slightly cheaper and guarantees no state mutation.\n- **Know your opcode costs** — A single misplaced SLOAD can cost 2100 gas; in a competitive MEV environment, that's the difference between profit and loss.\n\n## Conclusion\n\nThe EVM is elegant in its simplicity: a stack machine with 256-bit words, a flat bytecode format, and a gas metering system that prices every operation. Understanding this foundation — opcodes, the stack, and gas — is the prerequisite for everything that follows in this series: memory layout, storage optimization, security primitives, and finally writing raw Yul and Huff.\n\nIn the next article, we will explore the EVM's four data locations: stack, memory, storage, and calldata — and why choosing the right one determines whether your contract costs $0.50 or $50 to execute.","\u003Ch2 id=\"the-evm-is-a-stack-machine\">The EVM Is a Stack Machine\u003C\u002Fh2>\n\u003Cp>The Ethereum Virtual Machine is not like the x86 processor in your laptop. It has no registers. Instead, it is a \u003Cstrong>stack machine\u003C\u002Fstrong> — every computation pushes to or pops from a 1024-element stack where each element is a 256-bit (32-byte) word.\u003C\u002Fp>\n\u003Cp>When you call a smart contract, the EVM receives the contract’s bytecode — a flat sequence of single-byte opcodes — and begins executing from byte 0. There is no function table, no ELF header, no linking step. The bytecode is the program.\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002F\u002F Solidity:\n\u002F\u002F uint256 result = 2 + 3;\n\n\u002F\u002F Compiles to bytecode:\n\u002F\u002F PUSH1 0x02  PUSH1 0x03  ADD\n\n\u002F\u002F Stack trace:\n\u002F\u002F []           -&gt; PUSH1 0x02 -&gt; [2]\n\u002F\u002F [2]          -&gt; PUSH1 0x03 -&gt; [2, 3]\n\u002F\u002F [2, 3]       -&gt; ADD        -&gt; [5]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Every opcode consumes its operands from the top of the stack and pushes its result back. The ADD opcode pops two values, adds them, and pushes the sum. This is fundamentally different from register-based architectures where you specify source and destination registers.\u003C\u002Fp>\n\u003Ch2 id=\"opcode-categories\">Opcode Categories\u003C\u002Fh2>\n\u003Cp>The EVM defines roughly 140 opcodes, grouped into functional categories:\u003C\u002Fp>\n\u003Ch3>Arithmetic and Comparison\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>\u003Cstrong>ADD, SUB, MUL, DIV, MOD\u003C\u002Fstrong> — Basic 256-bit integer arithmetic. All cost 3 gas (the G_verylow tier).\u003C\u002Fli>\n\u003Cli>\u003Cstrong>SDIV, SMOD\u003C\u002Fstrong> — Signed division and modulo using two’s complement.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>ADDMOD, MULMOD\u003C\u002Fstrong> — Modular arithmetic: \u003Ccode>(a + b) % N\u003C\u002Fcode> and \u003Ccode>(a * b) % N\u003C\u002Fcode> in a single opcode. These are critical for elliptic curve operations and cost 8 gas.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>EXP\u003C\u002Fstrong> — Exponentiation. Costs 10 gas + 50 per byte in the exponent, making it one of the more expensive arithmetic opcodes.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>LT, GT, SLT, SGT, EQ, ISZERO\u003C\u002Fstrong> — Comparison opcodes that push 1 (true) or 0 (false).\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>Bitwise Operations\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>\u003Cstrong>AND, OR, XOR, NOT\u003C\u002Fstrong> — Bitwise logic, 3 gas each.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>SHL, SHR, SAR\u003C\u002Fstrong> — Shift left, logical shift right, arithmetic shift right (added in Constantinople, EIP-145). Before these existed, shifts required MUL\u002FDIV by powers of 2.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>BYTE\u003C\u002Fstrong> — Extract a single byte from a 32-byte word. \u003Ccode>BYTE(0, x)\u003C\u002Fcode> returns the most significant byte.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>Stack Manipulation\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>\u003Cstrong>POP\u003C\u002Fstrong> — Discard the top element.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>PUSH1 through PUSH32\u003C\u002Fstrong> — Push 1 to 32 bytes of immediate data onto the stack. PUSH1 is the most common opcode in deployed bytecode.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>DUP1 through DUP16\u003C\u002Fstrong> — Duplicate the Nth stack element to the top.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>SWAP1 through SWAP16\u003C\u002Fstrong> — Swap the top element with the Nth element below it.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>Environment and Block Information\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>\u003Cstrong>CALLER\u003C\u002Fstrong> (msg.sender), \u003Cstrong>CALLVALUE\u003C\u002Fstrong> (msg.value), \u003Cstrong>CALLDATALOAD\u003C\u002Fstrong>, \u003Cstrong>CALLDATASIZE\u003C\u002Fstrong>, \u003Cstrong>CALLDATACOPY\u003C\u002Fstrong> — Access transaction context.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>NUMBER\u003C\u002Fstrong>, \u003Cstrong>TIMESTAMP\u003C\u002Fstrong>, \u003Cstrong>BASEFEE\u003C\u002Fstrong>, \u003Cstrong>CHAINID\u003C\u002Fstrong> — Block-level information.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>BALANCE\u003C\u002Fstrong>, \u003Cstrong>EXTCODESIZE\u003C\u002Fstrong>, \u003Cstrong>EXTCODECOPY\u003C\u002Fstrong> — Query other accounts.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"the-gas-schedule\">The Gas Schedule\u003C\u002Fh2>\n\u003Cp>Every opcode has a gas cost. Gas serves two purposes: it prevents infinite loops (the halting problem) and it prices computational resources fairly.\u003C\u002Fp>\n\u003Cp>The gas costs fall into tiers:\u003C\u002Fp>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Tier\u003C\u002Fth>\u003Cth>Gas\u003C\u002Fth>\u003Cth>Examples\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>Zero\u003C\u002Ftd>\u003Ctd>0\u003C\u002Ftd>\u003Ctd>STOP, RETURN, REVERT\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Base\u003C\u002Ftd>\u003Ctd>2\u003C\u002Ftd>\u003Ctd>ADDRESS, ORIGIN, CALLER\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Very Low\u003C\u002Ftd>\u003Ctd>3\u003C\u002Ftd>\u003Ctd>ADD, SUB, LT, GT, AND, OR, POP\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Low\u003C\u002Ftd>\u003Ctd>5\u003C\u002Ftd>\u003Ctd>MUL, DIV, MOD\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Mid\u003C\u002Ftd>\u003Ctd>8\u003C\u002Ftd>\u003Ctd>ADDMOD, MULMOD, JUMP\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>High\u003C\u002Ftd>\u003Ctd>10\u003C\u002Ftd>\u003Ctd>JUMPI\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Special\u003C\u002Ftd>\u003Ctd>varies\u003C\u002Ftd>\u003Ctd>SLOAD, SSTORE, CALL, CREATE\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Cp>The expensive opcodes are the ones that touch state:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002F\u002F Gas costs for state access (post EIP-2929):\n\u002F\u002F SLOAD (cold):   2100 gas\n\u002F\u002F SLOAD (warm):    100 gas\n\u002F\u002F SSTORE (cold, 0-&gt;nonzero): 22100 gas\n\u002F\u002F SSTORE (warm):   100 gas (+ 20000 if 0-&gt;nonzero)\n\u002F\u002F CALL (cold):    2600 gas\n\u002F\u002F CALL (warm):     100 gas\n\u002F\u002F BALANCE (cold): 2600 gas\n\u002F\u002F BALANCE (warm):  100 gas\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"cold-vs-warm-access-eip-2929\">Cold vs Warm Access (EIP-2929)\u003C\u002Fh2>\n\u003Cp>EIP-2929 (Berlin upgrade, April 2021) introduced the concept of an \u003Cstrong>access list\u003C\u002Fstrong> — a per-transaction set of addresses and storage slots that have been touched.\u003C\u002Fp>\n\u003Cp>The first time you access a storage slot or external address within a transaction, it is “cold” and costs extra gas. Subsequent accesses are “warm” and cheap. This is why the order you read storage slots matters for gas optimization.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F In Solidity, this pattern is expensive:\nfunction bad() external view returns (uint256) {\n    \u002F\u002F First read of slot: 2100 gas (cold)\n    uint256 a = myStorage;\n    \u002F\u002F ... some logic ...\n    \u002F\u002F Second read: 100 gas (warm) -- compiler may or may not cache this\n    uint256 b = myStorage;\n    return a + b;\n}\n\n\u002F\u002F Cache in memory:\nfunction good() external view returns (uint256) {\n    uint256 cached = myStorage; \u002F\u002F 2100 gas (cold), only once\n    return cached + cached;     \u002F\u002F 6 gas (ADD + DUP)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"execution-flow-what-happens-in-a-transaction\">Execution Flow: What Happens in a Transaction\u003C\u002Fh2>\n\u003Cp>When you send a transaction that calls a contract, here is the full execution sequence:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Transaction validation\u003C\u002Fstrong> — Nonce check, balance &gt;= value + gas * gasPrice, signature verification.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Intrinsic gas deduction\u003C\u002Fstrong> — 21000 gas for the transaction itself, plus 16 gas per non-zero calldata byte and 4 per zero byte.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Context setup\u003C\u002Fstrong> — The EVM creates an execution context: code, calldata, caller, value, gas remaining.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Program counter starts at 0\u003C\u002Fstrong> — The EVM reads the opcode at position 0 and executes it.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Sequential execution\u003C\u002Fstrong> — Each opcode is executed, gas is deducted. JUMP and JUMPI allow non-linear control flow, but only to positions marked with JUMPDEST.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Termination\u003C\u002Fstrong> — Execution ends with STOP (success, no return data), RETURN (success, with return data), REVERT (failure, state reverted, return error data), or running out of gas.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>State commit or rollback\u003C\u002Fstrong> — On success, all state changes are committed. On revert, all changes within this call context are rolled back.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"the-program-counter-and-jump\">The Program Counter and JUMP\u003C\u002Fh2>\n\u003Cp>The program counter (PC) is an implicit register that tracks the current position in the bytecode. Most opcodes advance the PC by 1 (or by 1 + N for PUSH opcodes). Two opcodes modify the PC directly:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>JUMP\u003C\u002Fstrong> — Pops a destination from the stack, sets PC to that value. The destination must contain a JUMPDEST opcode or the transaction reverts.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>JUMPI\u003C\u002Fstrong> — Conditional jump. Pops destination and condition. If condition is nonzero, jump; otherwise continue sequentially.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>This is how the EVM implements if\u002Felse, loops, and function dispatch. The Solidity compiler generates a function selector that loads the first 4 bytes of calldata, compares against known function signatures, and JUMPIs to the matching code block.\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002F\u002F Function dispatch (simplified bytecode):\n\u002F\u002F CALLDATALOAD(0) -&gt; SHR(224) -&gt; function_selector\n\u002F\u002F DUP1 PUSH4 0xa9059cbb EQ PUSH2 0x00a4 JUMPI  \u002F\u002F transfer(address,uint256)\n\u002F\u002F DUP1 PUSH4 0x70a08231 EQ PUSH2 0x00d2 JUMPI  \u002F\u002F balanceOf(address)\n\u002F\u002F PUSH1 0x00 DUP1 REVERT                         \u002F\u002F fallback: revert\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"sub-calls-call-staticcall-delegatecall\">Sub-calls: CALL, STATICCALL, DELEGATECALL\u003C\u002Fh2>\n\u003Cp>Contracts can invoke other contracts using three call opcodes:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>CALL\u003C\u002Fstrong> — Standard call. Creates a new execution context with its own stack and memory. The callee runs independently; if it reverts, only its changes are rolled back.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>STATICCALL\u003C\u002Fstrong> — Read-only call (EIP-214). Any state-modifying opcode (SSTORE, CREATE, LOG, SELFDESTRUCT) within the callee causes an immediate revert.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>DELEGATECALL\u003C\u002Fstrong> — Executes the callee’s code but in the caller’s storage context. msg.sender and msg.value are preserved from the original call. This is how proxy patterns and libraries work.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Each call opcode takes 7 stack arguments: gas, address, value (except STATICCALL\u002FDELEGATECALL), argsOffset, argsLength, retOffset, retLength. The gas cost is 100 (warm) or 2600 (cold) plus any value transfer surcharge.\u003C\u002Fp>\n\u003Ch2 id=\"gas-refunds-and-their-limits\">Gas Refunds and Their Limits\u003C\u002Fh2>\n\u003Cp>Historically, clearing a storage slot from nonzero to zero gave a gas refund — incentivizing state cleanup. Post EIP-3529 (London upgrade), the maximum refund is capped at 20% of total gas used (down from 50%). This killed the gas token arbitrage (CHI, GST2) that exploited refunds for profit.\u003C\u002Fp>\n\u003Cp>The remaining refund sources:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>SSTORE: setting a nonzero slot back to zero refunds 4800 gas.\u003C\u002Fli>\n\u003Cli>SELFDESTRUCT: removed as a refund source in EIP-3529.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"practical-implications-for-mev\">Practical Implications for MEV\u003C\u002Fh2>\n\u003Cp>If you are building MEV bots, understanding the EVM at the opcode level is not optional — it is a competitive requirement. Every gas unit saved in your bot’s execution is profit margin. Key insights:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Simulate before submitting\u003C\u002Fstrong> — Use \u003Ccode>eth_call\u003C\u002Fcode> or a local EVM (revm, EVMONE) to trace execution and know the exact gas cost before your bundle hits the builder.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Minimize cold access\u003C\u002Fstrong> — Pre-warm storage slots via access lists (EIP-2930).\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Use STATICCALL for reads\u003C\u002Fstrong> — Slightly cheaper and guarantees no state mutation.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Know your opcode costs\u003C\u002Fstrong> — A single misplaced SLOAD can cost 2100 gas; in a competitive MEV environment, that’s the difference between profit and loss.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"conclusion\">Conclusion\u003C\u002Fh2>\n\u003Cp>The EVM is elegant in its simplicity: a stack machine with 256-bit words, a flat bytecode format, and a gas metering system that prices every operation. Understanding this foundation — opcodes, the stack, and gas — is the prerequisite for everything that follows in this series: memory layout, storage optimization, security primitives, and finally writing raw Yul and Huff.\u003C\u002Fp>\n\u003Cp>In the next article, we will explore the EVM’s four data locations: stack, memory, storage, and calldata — and why choosing the right one determines whether your contract costs $0.50 or $50 to execute.\u003C\u002Fp>\n","en","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:22.639197Z","A low-level walkthrough of the Ethereum Virtual Machine: opcodes, stack operations, gas metering, and cold vs warm access explained.","EVM opcodes",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"]