Subroutine Logo
Subroutine
← Back to Articles Web3 & Crypto Advanced 10 min read

The EVM Execution Pipeline: Stack Mechanics, Gas Scheduling & Transient Storage

How the Ethereum Virtual Machine executes 256-bit word bytecode, dynamically calculates gas costs, manages persistent vs transient storage (EIP-1153), and enforces reentrancy protection.

Published: 2026-09-07
#Web3 & Crypto#Ethereum#EVM#Solidity#Bytecode#Gas#Rust#Systems

The Ethereum Virtual Machine (EVM) is a quasi-Turing-complete, deterministic state machine that forms the consensus execution layer of the Ethereum blockchain.

Unlike register-based architectures (x86_64, ARM64, and WebAssembly) or byte-oriented virtual machines, the EVM natively computes on 256-bit integer words. This choice was deliberately made to facilitate native 256-bit cryptographic operations (Keccak-256 hashes, secp256k1 elliptic curve signatures) without multiple arithmetic dispatch cycles.


1. Summary & Virtual Machine Architecture

Memory ComponentPersistenceRead / Write CostAddressing / Sizing
StackCall frame local3 gas per PUSH / POPMax 1024 slots of 256-bit words
Volatile MemoryMessage call lifetimeLinear + quadratic expansionByte-addressable, zeroed per call
Transient Storage (EIP-1153)Transaction lifetime100 gas flat (TLOAD / TSTORE)256-bit key-value mapping in RAM
Persistent StoragePermanent on-chain2,100 to 20,000 gas (SLOAD / SSTORE)256-bit key-value slots (Patricia Trie)
CalldataRead-only input4 gas (zero byte) / 16 gas (non-zero)Byte-addressable transaction payload

2. The Four EVM Storage Domains

graph TD
    subgraph ExecutionFrame["EVM Call Execution Frame"]
    STK["1. Stack: 1024 x 32-Byte Slots (LIFO)"]
    MEM["2. Memory: Byte-Addressable Linear Buffer"]
    end

    subgraph TransactionScope["Transaction-Level Scope"]
    TS["3. Transient Storage (TSTORE / TLOAD)"]
    end

    subgraph StateTrie["Global State (On-Disk LevelDB / Pebble)"]
    PS["4. Persistent State Trie (SSTORE / SLOAD)"]
    end

    STK -->|Memory Expansion| MEM
    STK -->|Intra-Tx State| TS
    STK -->|Permanent Consensus State| PS

3. Dynamic Gas Scheduling & Memory Expansion

To solve the Halting Problem, the EVM attaches an atomic resource budget to every transaction: Gas. Every bytecode instruction consumes a deterministic amount of gas. If gas drops below zero, the entire transaction reverts, undoing all state mutations while still consuming the transaction fee.

Memory Expansion Quadratic Cost Function

Volatile EVM memory expands dynamically in 32-byte word increments. The cumulative gas cost Cmem(a)C_{\text{mem}}(a) for allocating aa words of memory is non-linear:

Cmem(a)=3a+a2512C_{\text{mem}}(a) = 3a + \left\lfloor \frac{a^2}{512} \right\rfloor

When expanding memory from aa words to bb words, the transaction pays:

ΔC=Cmem(b)Cmem(a)\Delta C = C_{\text{mem}}(b) - C_{\text{mem}}(a)

The quadratic term prevents adversarial contracts from flooding validator RAM with unbounded dynamic allocations.


4. EIP-1153: Transient Storage (TLOAD & TSTORE)

Historically, smart contracts needing temporary state across multiple internal calls within a single transaction (e.g. reentrancy guards, ERC-20 permit approvals, tick caches in Uniswap v4) had to write to persistent storage via SSTORE.

Writing to persistent storage requires disk modification logs and state trie dirtying, costing 2,100 to 20,000 gas per update.

EIP-1153 (Cancun Hard Fork) introduced two new opcodes:

  • TSTORE (0x5d): Writes a 32-byte value to transient storage. Cost: 100 gas.
  • TLOAD (0x5c): Reads a 32-byte value from transient storage. Cost: 100 gas.

Transient storage is stored in validator RAM and discarded the exact instant the root transaction finishes, eliminating disk I/O and reducing reentrancy protection overhead by over 95%.

Transient Storage Reentrancy Defense

Using TSTORE and TLOAD for non-reentrant function modifiers avoids polluting the global Ethereum state trie, reducing reentrancy guard gas consumption from over 2,200 gas down to exactly 200 gas per invocation.


5. Dual-Language Implementation

EVM Stack Interpreter & Transient Storage Guard
evm_core.rs
Rust (EVM Stack & Gas Step Interpreter)
pub struct EvmStack {
    data: Vec<[u8; 32]>,
}

impl EvmStack {
    pub fn new() -> Self {
        Self { data: Vec::with_capacity(1024) }
    }

    pub fn push(&mut self, val: [u8; 32]) -> Result<(), &'static str> {
        if self.data.len() >= 1024 {
            return Err("StackOverflow");
        }
        self.data.push(val);
        Ok(())
    }

    pub fn pop(&mut self) -> Result<[u8; 32], &'static str> {
        self.data.pop().ok_or("StackUnderflow")
    }
}

pub struct EvmInterpreter {
    pub gas_remaining: u64,
    pub stack: EvmStack,
}

impl EvmInterpreter {
    pub fn step(&mut self, opcode: u8) -> Result<(), &'static str> {
        match opcode {
            0x01 => { // ADD
                if self.gas_remaining < 3 { return Err("OutOfGas"); }
                self.gas_remaining -= 3;
                let a = self.stack.pop()?;
                let b = self.stack.pop()?;
                // 256-bit wrapping addition
                let mut res = [0u8; 32];
                let mut carry = 0u16;
                for i in (0..32).rev() {
                    let sum = (a[i] as u16) + (b[i] as u16) + carry;
                    res[i] = (sum & 0xFF) as u8;
                    carry = sum >> 8;
                }
                self.stack.push(res)?;
            }
            _ => return Err("UnknownOpcode"),
        }
        Ok(())
    }
}

6. Architectural Guidance

  • For temporary intra-transaction state passing (such as Flash Loan accounting or routing reentrancy checks), migrate from persistent storage to EIP-1153 TSTORE / TLOAD.
  • Keep memory expansion tight: reading or copying calldata at high offsets triggers the quadratic expansion formula, resulting in sudden out-of-gas failures on large payloads.
  • Always use PUSH0 (EIP-3855) instead of PUSH1 0x00 to save 1 byte of code size and 1 unit of gas on zero-pushes.