Skip to main content
Cover image for EVM Opcodes 101: What Happens When You Send a Transaction

EVM Opcodes 101: What Happens When You Send a Transaction

EVM by Antoine · March 12, 2026 · 8 min read

A developer ships a token swap function. Works perfectly in testing. On mainnet, it burns $47 in gas for what should be a $3 operation. The culprit? An SSTORE inside a loop, writing to cold storage on every iteration instead of caching in memory first. Fifteen lines of Solidity, and the most expensive one is invisible unless you know where to look. Understanding opcodes turns that mystery into arithmetic.

EVM opcodes are the low-level, single-byte instructions that the Ethereum Virtual Machine executes. Every Solidity function compiles down to a sequence of these roughly 140 opcodes, covering arithmetic, memory access, persistent storage, and control flow. They’re the layer where gas costs are actually determined and where failed transactions can be debugged.

Your transaction’s journey before a single opcode fires

Before the EVM touches your transaction, it goes through a gauntlet of validation checks.

Your wallet packs the transaction using RLP serialization1, Ethereum’s compact binary format. Recipient, value, calldata, gas limit, nonce: everything compressed into a byte stream, then signed with your private key using ECDSA2.

The signed transaction hits the mempool. A validator pulls it out and runs three checks:

  • Nonce: does it match your next expected nonce? One ahead or behind, and it’s rejected or queued.
  • Signature recovery: does ECDSA produce an address with enough ETH to cover gas limit * gas price + value?
  • Intrinsic gas floor: every transaction costs at least 21,000 gas before a single opcode runs. Contract creation costs 32,000. Each calldata byte adds 4 gas (zero bytes) or 16 gas (non-zero), as defined in the Yellow Paper3.

Here’s what catches people off guard: gas is deducted before execution starts. Your full gas limit * gas price is gone from your balance the moment execution begins. You get the unused portion back, but only after. Think of it as a security deposit, not a running meter.

Three scratch pads: the EVM’s workspace

The EVM sets up an execution context, essentially a tiny sandboxed computer with three memory regions. Each has different rules, different lifetimes, and wildly different costs. Understanding the difference is half of understanding gas.

The stack is a calculator’s display. Last-in, first-out, up to 1,024 items, each 32 bytes. Every opcode reads from and writes to the stack. It starts empty, costs almost nothing, and vanishes when the call ends.

Memory is a whiteboard. A linear byte array, all zeros at the start, that grows as you need it. Write whatever you want, erase it when you’re done. Cheap at first, but expansion costs scale quadratically, like a parking garage that doubles its rate with every floor.

Storage is carved in stone. A key-value store mapping 256-bit slots to 256-bit values, backed by a Merkle Patricia Trie4 that every node on the network maintains. It persists between transactions. It’s the only thing that persists. And it costs 4,000x more than basic computation.

Program counter starts at zero. Bytecode loads. Execution begins.

What does bytecode look like? Take a dead-simple Solidity function:

contract Store {
    uint256 public value;

    function set(uint256 _value) public {
        value = _value;
    }
}

That set function compiles to essentially two instructions:

PUSH1 0x00   // Push storage slot 0
SSTORE       // Store the input value there

Two opcodes. That’s the whole function at the machine level. Everything else, the function selector, the calldata decoding, the stack shuffling, is boilerplate the compiler generates around it.

The opcodes that matter

There are about 140 opcodes5. You don’t need to memorize them. But knowing five categories explains 95% of what you’ll see in a transaction trace.

Arithmetic: the penny aisle

ADD pops two stack values, pushes their sum. MUL multiplies. LT, GT, EQ handle comparisons. Cost: 3 to 5 gas each. Ten arithmetic opcodes cost about 40 gas total. If someone tells you to “optimize your math,” they’re chasing pennies.

Stack operations: the plumbing

PUSH1 through PUSH32 load constants onto the stack. DUP1 clones the top value. SWAP1 flips the top two. All 3 gas. These are the only opcodes that read directly from bytecode instead of the stack, and they’re everywhere: a typical function has more PUSH instructions than anything else.

Memory: cheap until it isn’t

MLOAD reads 32 bytes from a memory offset. MSTORE writes 32 bytes. Base cost: 3 gas. But touch a new offset and you pay expansion:

cost = (memory_size_words² / 512) + (3 * memory_size_words)

That squared term is the key. Your first few kilobytes of memory cost almost nothing. Push into hundreds of kilobytes and each expansion costs more than the last. It’s why Solidity won’t let you copy a large storage array into memory without warning you about gas.

Storage: where the real money goes

SLOAD reads from storage. SSTORE writes. This is where gas costs stop being theoretical.

OperationGas costContext
SLOAD (cold)2,100First access, disk lookup required
SLOAD (warm)100Subsequent access, cached
SSTORE (zero to non-zero)20,000Creating new state
SSTORE (non-zero to non-zero)5,000Updating existing state

That 20,000 gas for a new SSTORE is not a typo. Writing a single 32-byte value to storage costs almost as much gas as the intrinsic cost of the entire transaction (21,000). The reason is straightforward: that value will be stored by every full node on the network, indefinitely7. You’re not renting space. You’re buying permanent real estate on thousands of hard drives.

Cold vs warm is a concept from EIP-29296, proposed by Vitalik and Martin Swende to align gas prices with actual I/O costs. First access to a storage slot requires a trie traversal (disk I/O), so it costs 2,100. After that, the slot sits in a warm cache: 100 gas. This single distinction explains why access lists can save you gas on complex transactions, and why the order you touch storage slots can matter more than the logic itself.

Control flow: the wiring

JUMP repositions the program counter. JUMPI does it conditionally. This is how if/else and loops work at the bytecode level. JUMPDEST marks valid landing spots, a guardrail that prevents jumping into the middle of a PUSH instruction’s data3.

CALL invokes another contract. CREATE deploys one. RETURN ends execution and returns data. REVERT does the same but rolls back every state change.

OpcodeHexGasWhat it does
ADD0x013Addition
MUL0x025Multiplication
EQ0x143Equality check
PUSH10x603Push 1 byte onto stack
MLOAD0x513*Read from memory
MSTORE0x523*Write to memory
SLOAD0x542,100/100Read from storage
SSTORE0x5520,000/5,000Write to storage
JUMP0x568Unconditional jump
CALL0xF1variesCall another contract
RETURN0xF30Return output data
REVERT0xFD0Revert state changes

*Plus memory expansion cost.

The gas hierarchy: a mental model

Once you see the numbers, a clear hierarchy emerges:

Computation (3-5 gas): pure math, no side effects. Optimize last, if ever.

Memory (3 gas + quadratic expansion): your scratch pad. Cheap for small allocations, punishing for large ones. But it’s temporary: gone when the call frame ends, so the network carries no long-term cost.

Storage (2,100-20,000 gas): the expensive real estate. Permanent, replicated across every node. This is where 80%+ of your gas goes in any state-changing transaction.

This hierarchy gives you one optimization rule that covers most cases: never touch storage inside a loop. Read the value into a memory variable, do your work, write back once. You’ll save thousands of gas per iteration. That’s the difference between the $3 transaction and the $47 one from the opening.

One more thing: since EIP-35298 (London hard fork), gas refunds for clearing storage are capped at 20% of total gas used. The old gas-token trick, filling storage slots when gas is cheap and clearing them for refunds later, is dead.

When things go wrong: success vs revert

Execution ends one of two ways, and the difference is night and day for debugging.

Success: all state changes commit. Storage writes, balance transfers, new contracts, all permanent. Unused gas returns to the sender.

Revert or out-of-gas: everything rolls back. The transaction still appears on-chain (the validator did real work processing it), but none of the state changes stick. The consumed gas is gone.

Here’s where the two failure modes diverge. REVERT usually carries a reason: a Solidity require message or a custom error9. You get a string telling you what went wrong. Out-of-gas gives you nothing. Just “failed.” No breadcrumbs, no stack trace, no hint about which opcode drained the last drop of gas.

That’s where most developers hit a wall. Something broke somewhere inside the bytecode, and all you have is a transaction hash and an empty error field.

Transaction traces: the EVM’s flight recorder

Everything that happened during execution, every opcode, every storage read, every gas charge, gets captured in a transaction trace.

When Transaction X calls Contract A, which calls Contract B, which calls Contract C, which reverts, the trace shows the entire call tree. Function names decoded, parameters visible, return values captured, gas consumption broken down at each level. You can pinpoint the exact opcode where things went sideways.

If you’ve clicked “Internal Txns” on Etherscan, you’ve seen a simplified version of this. Full tracing gives you the complete picture: not just what was called, but what it received, what it returned, and exactly where and why it failed.

We built Ethernal for exactly this. Connect a local Hardhat or Anvil chain, send a transaction, and get full traces in under a minute. No configuration, no node setup. Free tier covers most development workflows.

Bottom line

EVM opcodes aren’t trivia for compiler engineers. They’re the actual instructions your users pay for with every transaction, and the layer where gas optimization and debugging actually happen. Stack, memory, storage: three tiers of cost, one clear hierarchy. Master that hierarchy and you’ll write cheaper contracts and debug failed transactions in minutes instead of hours.

Deploy something. Send a transaction. Open the trace. Seeing those opcodes fire for your own code is what makes all of this click.


References

1. Ethereum Foundation. “RLP Serialization.” ethereum.org, 2024.

2. Ethereum Foundation. “Transactions.” ethereum.org, 2024.

3. Wood, G. “Ethereum: A Secure Decentralised Generalised Transaction Ledger (Yellow Paper).” ethereum.github.io, 2024.

4. Ethereum Foundation. “Patricia Merkle Tries.” ethereum.org, 2024.

5. EVM Codes Contributors. “EVM Opcodes Interactive Reference.” evm.codes, 2024.

6. Buterin, V. and Swende, M. “EIP-2929: Gas Cost Increases for State Access Opcodes.” Ethereum Improvement Proposals, 2020.

7. Buterin, V. “A Theory of Ethereum State Size Management.” ethresear.ch, 2021.

8. Buterin, V. and Swende, M. “EIP-3529: Reduction in Refunds.” Ethereum Improvement Proposals, 2021.

9. Solidity Team. “Solidity v0.8.4 Release: Custom Errors.” soliditylang.org, 2021.

Ready to explore your chain?

Connect your RPC and get a fully working block explorer in under 5 minutes. Free to start.

Get Started