Lecture 3: EVM Execution Model
Instructor: Yu Feng, UCSB
CS190: Blockchain Programming and Applications
Understanding how the Ethereum Virtual Machine executes smart contract code and manages state transitions through deterministic computation.
Motivation: Ethereum as a State Machine
Replicated State Machine
Ethereum operates as a distributed state machine where every node maintains an identical copy of the global state. Consensus ensures all nodes process transactions in the same order.
Deterministic Transitions
Transactions serve as inputs that trigger state transitions. The EVM guarantees deterministic execution—given the same input and initial state, every node reaches the same final state.
Sandboxed Execution
The EVM is a stack-based virtual machine that executes code in isolation. This sandboxed environment prevents malicious code from affecting the host system or other contracts.
Our goal in this lecture is to understand the precise mechanics of how bytecode instructions transform into state updates, enabling smart contract functionality on the blockchain.
Inside the EVM: Core Components
The Ethereum Virtual Machine operates on four fundamental data structures that work together to execute smart contract code. Understanding these components is essential for comprehending how the EVM processes instructions.
1
Stack
A last-in-first-out data structure with a maximum capacity of 1024 items. Each item is a 256-bit word. Most EVM operations manipulate the stack—pushing values, popping operands, and performing computations.
2
Memory
Temporary byte-addressed storage that expands dynamically during execution. Memory is volatile and cleared after each call completes. It's used for intermediate calculations and preparing data for return values.
3
Calldata
Read-only input bytes passed to the contract during a transaction or call. Contains function identifiers and encoded parameters. The contract must explicitly load calldata values onto the stack for processing.
4
Storage
Storage is the EVM’s persistent key value map from 256 bit keys to 256 bit values. Only storage changes survive after the call finishes.

Important: Both memory and stack are cleared after each call completes. Only storage persists across transactions.
Stack Operations: A Warm-Up Example
Let's trace through a sequence of stack manipulation opcodes to understand how the EVM processes instructions.
Instruction Sequence
PUSH1 0xAA PUSH1 0xBB PUSH1 0xCC SWAP1 POP POP
Each instruction performs a single, indivisible operation on the stack. The EVM executes these sequentially, updating the stack state after each step.
Stack State Trace

Key Insight: Each opcode is atomic and deterministic. Given the same initial stack, the sequence always produces the same final state.
Arithmetic Operations Example
The EVM performs arithmetic using 256-bit unsigned integers with modular arithmetic. Let's examine a simple addition operation to understand how arithmetic opcodes manipulate the stack.
1
Push Operands
PUSH1 0x02 PUSH1 0x03
Stack: [0x03, 0x02]
2
Execute ADD
ADD
Pops 0x03 and 0x02, computes sum modulo 2²⁵⁶
3
Result
Pushes 0x05 onto stack
Stack: [0x05]
Before ADD
After ADD
All arithmetic operations in the EVM use modular arithmetic with modulus 2²⁵⁶. This means values wrap around at the maximum 256-bit unsigned integer, preventing overflow exceptions but requiring careful handling of edge cases in smart contract code.
Control Flow with Conditional Jumps
The EVM implements control flow through jump instructions. Unlike high-level languages, the EVM uses explicit jump targets marked by the JUMPDEST opcode. Let's examine a simple conditional: if x ≠ 0, return 1; else return 0.
Bytecode Program
00: PUSH1 0x08 02: PUSH1 0x00 03: CALLDATALOAD 04: SWAP1 05: ISZERO 06: JUMPI 07: STOP 08: JUMPDEST 09: PUSH1 0x01 0B: PUSH1 0x00 0D: MSTORE 0E: PUSH1 0x20 10: PUSH1 0x00 12: RETURN

JUMPDEST marks valid jump targets. Jumping to any other position causes execution to revert.
Execution Flow
  • Load input value from calldata
  • Check if zero with ISZERO
  • JUMPI pops condition and destination
  • If condition is true (x = 0), jump to STOP
  • If false (x ≠ 0), continue to JUMPDEST
  • Store 1 in memory and return 32 bytes
This pattern forms the foundation for implementing if-else statements, loops, and other control structures in smart contracts compiled to EVM bytecode.
Frequently Used Opcodes Reference
The EVM instruction set contains over 140 opcodes organized into functional categories. Here are the most commonly used instructions you'll encounter when analyzing or writing EVM bytecode.
Mastering these opcodes is essential for understanding how high-level Solidity code compiles to bytecode and for optimizing gas consumption in production contracts.
Data Flow: Calldata → Memory → Return
Unlike high-level languages with named variables, the EVM requires explicit movement of data between its storage areas. Understanding this data flow is crucial for reading bytecode and optimizing contract execution.
01
Load from Calldata
Input data arrives as raw bytes in calldata. Use CALLDATALOAD to read 32 bytes onto the stack, or CALLDATACOPY to transfer chunks directly to memory.
02
Process on Stack
Perform computations using stack operations. The stack serves as the working area for all arithmetic, logical, and comparison operations.
03
Store in Memory
Use MSTORE to write 32-byte words to memory at specific offsets. Memory acts as a temporary buffer for organizing output data.
04
Return Results
The RETURN opcode specifies an offset and length pair indicating which bytes from memory to return. This data becomes the output of the transaction or call.

Key Point: There are no implicit variables. Every data movement must be explicitly programmed using opcodes.
Storage: The Persistent Layer
Storage is the only data location in the EVM that persists across transactions. It functions as a key-value store where both keys and values are 256-bit words, providing each contract with 2²⁵⁶ storage slots.
Structure
Storage is organized as a mapping from 256-bit keys to 256-bit values. Initially, all slots contain zero. Each contract has its own isolated storage space.
Persistence
Only storage changes persist beyond the current call. When a transaction completes successfully, storage modifications become part of the global state replicated across all nodes.
Cost
Storage operations are the most expensive in the EVM because every change must be replicated, verified, and stored permanently across thousands of nodes in the network.
Efficient storage usage is critical for gas optimization. Developers pack multiple values into single slots, batch updates, and minimize writes to reduce costs. Understanding storage layout is essential for both security auditing and performance optimization.
Comparing Data Locations in the EVM
The EVM provides three distinct data locations, each with different characteristics that affect performance, cost, and persistence. Choosing the right location is crucial for efficient contract design.
As a general rule: use the stack for immediate operations, memory for temporary data structures, and storage only for values that must persist across transactions.
Example: Writing to Storage Slot 0
Let's trace through a simple program that stores the value 0x2a (decimal 42) into storage slot 0. This demonstrates the basic pattern for persisting data in contract storage.
Bytecode Sequence
PUSH1 0x00 PUSH1 0x2a SWAP1 SSTORE STOP
The SSTORE opcode expects the stack to have the storage slot at the top and the value to store in the second position.
Stack Trace
After SSTORE: storage[0x00] = 0x2a

Critical Detail: SSTORE expects slot at stack top, value at second position. The SWAP1 instruction corrects the order before calling SSTORE.
This storage write persists after the transaction completes. Any future transaction can read this value using SLOAD, even across different blocks or years of blockchain history.
Example: Reading Storage and Returning Data
This example demonstrates the complete data flow from storage through memory to the return value. We'll read the value from storage slot 0, place it in memory, and return it to the caller.
Complete Bytecode
PUSH1 0x00 SLOAD PUSH1 0x00 MSTORE PUSH1 0x20 PUSH1 0x00 RETURN
This six-instruction sequence loads from storage, buffers through memory, and returns 32 bytes to the caller.
Step-by-Step Execution
  1. SLOAD: Reads storage[0x00] onto stack
  1. MSTORE: Writes value to memory[0x00]
  1. RETURN: Returns 32 bytes (0x20) starting at memory offset 0x00
The RETURN opcode takes two arguments: offset and length. Here we return the full 32-byte word from memory.
1
Storage → Stack
SLOAD retrieves persistent value
2
Stack → Memory
MSTORE buffers for return
3
Memory → Caller
RETURN sends 32 bytes back
This pattern—load from storage, prepare in memory, return to caller—appears in virtually every getter function compiled from Solidity. Understanding this flow is essential for reading contract bytecode.
Why Gas Matters: Ensuring Termination and Fair Resource Pricing
Gas serves two critical functions in the EVM: it prevents infinite loops that would halt the network, and it fairly compensates miners for the computational resources required to execute transactions.
Halting Problem Solution
Every instruction costs gas, so programs with finite gas budgets must eventually terminate. This solves the halting problem for the EVM—no program can run forever.
Resource Pricing
Gas costs reflect real computational resources. Simple operations like arithmetic cost very little, while expensive operations like storage writes cost significantly more.
DoS Protection
Attackers cannot flood the network with computationally expensive transactions without paying proportional fees. Gas makes denial-of-service attacks economically infeasible.
The dramatic cost difference between storage operations and other instructions incentivizes developers to minimize storage usage and optimize their contracts for gas efficiency.
Opcode Gas Costs: A Detailed Reference
Understanding the gas cost of individual opcodes is essential for optimizing smart contracts and predicting transaction costs. Here's a reference table showing approximate costs for common operations.

Note: Memory expansion costs grow quadratically as memory usage increases. Storage costs vary significantly based on whether slots are accessed for the first time (cold) or subsequently (warm) within a transaction.
Cold vs. Warm Access: Gas Optimization Opportunity
The EVM implements an access list mechanism that charges different gas costs for the first access to a storage slot or contract address (cold) versus subsequent accesses (warm) within the same transaction.
Cold Access
  • First time accessing a storage slot in a transaction
  • First time calling a contract address
  • Much higher gas cost (2,100 vs. 100 for SLOAD)
  • Compensates for loading data from disk
Warm Access
  • Subsequent accesses to same slot/address
  • Data already cached in memory
  • Significantly lower gas cost
  • Rewards batching operations
Optimization Strategy
Batch multiple reads from the same storage slot. Load once, use multiple times. This pattern saves gas by converting expensive cold reads into cheap warm reads.
Write Batching
Accumulate changes in memory, then commit once to storage. Minimize the number of SSTORE operations, since each write is expensive regardless of warm/cold status.
Complete Example: Compute and Store Result
Let's trace through a complete program that performs arithmetic and stores the result, analyzing both the execution flow and gas consumption. This example combines arithmetic operations with storage writes.
Full Program
PUSH1 0x02 // 3 gas PUSH1 0x03 // 3 gas ADD // 3 gas PUSH1 0x00 // 3 gas SWAP1 // 3 gas SSTORE // 22,100 or 2,900 gas STOP // 0 gas
This seven-instruction sequence computes 2 + 3 and stores the result (5) in storage slot 0.
Stack Evolution
22,115
Cold Write
Total gas if storage slot never accessed before
2,915
Warm Write
Total gas if slot accessed earlier in transaction
7.6x
Cost Ratio
Cold writes cost nearly 8× more than warm writes
The vast majority of gas consumption comes from the SSTORE operation. The arithmetic and stack manipulation costs are negligible in comparison, demonstrating why storage optimization is the primary focus for gas-efficient contracts.
Key Takeaways: Understanding the EVM
Deterministic Stack Machine
The EVM is a stack-based virtual machine that executes bytecode deterministically. Given the same initial state and input, every node in the network reaches the same final state through identical instruction sequences.
Explicit Data Movement
Data flows explicitly through the system: calldata provides inputs, the stack serves as the working area, memory acts as a temporary buffer, and storage persists state changes. There are no implicit variables or automatic data transfers.
Storage as Sole Persistent Layer
Only storage survives after a transaction completes. Stack and memory are cleared after each call. Any state that must persist across transactions must be explicitly written to storage slots using SSTORE.
Gas Enforces Economic Incentives
Gas serves dual purposes: preventing infinite loops (solving the halting problem) and pricing computational resources fairly. Storage operations cost orders of magnitude more than arithmetic, incentivizing efficient contract design.
Tools for Hands-On Practice
The best way to internalize EVM concepts is through hands-on experimentation. These tools let you write bytecode, trace execution, and visualize how instructions transform the machine state.
EVM Codes Reference
evm.codes provides a comprehensive opcode reference with detailed descriptions, gas costs, stack effects, and examples for every instruction. Essential bookmark for EVM development.
EVM Playground
EVM Playground offers an interactive environment where you can write bytecode and watch step-by-step execution. Visualizes stack, memory, storage, and gas consumption in real-time.
Practice Exercises
Start with simple programs: add two numbers, conditional branching, reading/writing storage. Gradually increase complexity by combining opcodes. Trace every instruction and predict stack states before checking results.

Recommended Exercise: Paste the examples from this lecture into the EVM Playground and step through execution. Watch how the stack, memory, and storage change with each instruction. This hands-on experience is invaluable for building intuition.
Mastering the EVM requires practice. Dedicate time to writing raw bytecode, tracing execution, and understanding how high-level Solidity code compiles to these low-level instructions. The investment pays dividends in debugging, optimization, and security analysis.
Looking Ahead: From EVM to Solidity
In our next lecture, we'll build on this foundation by exploring Solidity, Ethereum's high-level programming language. You'll see how Solidity abstracts away low-level bytecode details while still compiling to the EVM instructions we've studied.
01
Solidity Language Basics
Variables, data types, functions, and modifiers. Learn how Solidity's familiar syntax maps to EVM opcodes.
02
State Variables and Storage Layout
Understanding how Solidity packs variables into storage slots and optimizes gas consumption.
03
Function Visibility and Modifiers
Access control patterns, function modifiers, and how they compile to bytecode checks.
Armed with your understanding of the EVM's execution model, you'll be able to write more efficient Solidity code, debug issues at the bytecode level, and understand exactly what happens when your contracts execute on-chain.
"Understanding the EVM is like understanding assembly language—it gives you the foundation to master any higher-level abstraction built on top of it."