The Ethereum Virtual Machine
The Engine That Runs Every Smart Contract

What Is the EVM?

The Ethereum Virtual Machine (EVM) is the runtime environment that executes smart contract code on Ethereum. Every node in the network runs an identical copy of the EVM. When a transaction targets a smart contract, the node loads the contract's bytecode from the blockchain state and feeds it to the EVM, which processes it instruction by instruction. Because every node executes the same bytecode with the same inputs and the same rules, they all arrive at the same result. This deterministic execution is what allows thousands of independent machines to agree on the state of the blockchain without trusting each other.

The EVM is a stack-based virtual machine. Unlike register-based architectures (like x86 or ARM), it does not have named registers. Instead, all computations happen by pushing values onto a stack and popping them off. It also has access to two other data regions: memory (a temporary byte array that exists only for the duration of the call) and storage (a persistent key-value store that lives on-chain between transactions).

From Source Code to Bytecode Solidity or Vyper .sol / .vy compile solc compiler Bytecode 60 80 60 40 52 34 80 15 61 00 10 57 60 00 fd deployed on-chain Inside the EVM Ethereum Virtual Machine Stack max 1024 items 256-bit words 0x00...0064 0x00...0001 0xa9059cbb ... PUSH / POP Memory byte-addressable volatile (per call) 0x00 0x20 0x40 0x60 0x80 ... expands as needed (costs more gas) MLOAD / MSTORE Storage key-value (256-bit) persistent on-chain slot 0 = owner slot 1 = totalSupply slot 2 = balances SLOAD / SSTORE (most expensive ops) Gas metered Execution: Program Counter reads opcodes one by one PUSH1 0x60 ADD SSTORE PUSH1 CALL ... RETURN PC 3 gas 3 gas 20000 gas! 2600+ gas Execution Context msg.sender who called this contract msg.value ETH sent with the call msg.data calldata (function selector + args) block.number current block height tx.origin the EOA that started the tx gasleft() remaining gas address(this) contract's own address block.timestamp slot timestamp tx.gasprice gas price of the tx block.chainid 1 for mainnet

From Solidity to Opcodes

Developers write smart contracts in high-level languages like Solidity or Vyper. The compiler (solc for Solidity) translates the source code into bytecode, a sequence of low-level instructions called opcodes. Each opcode is a single byte (hence the name). For example, 0x60 is PUSH1 (push one byte onto the stack), 0x01 is ADD, and 0x55 is SSTORE (write to storage). The EVM has about 140 opcodes covering arithmetic, comparison, memory access, storage access, control flow, logging, and inter-contract communication.

Solidity / EVM
// Solidity
uint256 x = 1 + 2;

// Compiles roughly to these EVM opcodes
PUSH1 0x01    // push 1 onto the stack
PUSH1 0x02    // push 2 onto the stack
ADD           // pop both, push 3
PUSH1 0x00    // storage slot 0
SSTORE        // store 3 at slot 0

The Three Data Regions

Stack

The stack is where all computation happens. It holds up to 1024 items, each 256 bits (32 bytes) wide. Most opcodes pop their inputs from the stack and push their result back onto it. For instance, ADD pops two values, adds them, and pushes the sum. The stack is the only place where the EVM can do arithmetic, comparisons, and bitwise operations.

Memory

Memory is a linear byte array that starts empty and can grow as needed. It is volatile: it only exists for the duration of the current call and is wiped when execution returns. Contracts use memory for intermediate computations, ABI encoding/decoding, and passing data between internal function calls. Accessing memory is cheap at first but the cost increases quadratically as you expand it, which prevents contracts from using unbounded amounts.

Storage

Storage is a key-value mapping where both keys and values are 256 bits. Unlike memory, storage is persistent: values written to storage survive after the transaction ends and remain on-chain until explicitly overwritten. This is where contract state lives (balances, ownership, mappings, etc.). Storage operations are the most expensive in the EVM: writing a new value to a fresh slot costs 20,000 gas, while a simple addition costs only 3 gas. This pricing reflects the real cost to the network, since storage must be maintained by every node indefinitely.

Gas: Metering Every Instruction

Every opcode has a gas cost. When you submit a transaction, you specify a gas limit (the maximum amount of computation you are willing to pay for) and a gas price (how much ETH you pay per unit of gas). As the EVM executes each opcode, it subtracts the corresponding gas cost from the remaining budget. If the budget hits zero before execution finishes, the EVM halts with an "out of gas" error: all state changes revert, but the gas is still consumed (the validator still gets paid for the work they did).

This gas mechanism serves two purposes. First, it prevents infinite loops and denial-of-service attacks. Since every instruction costs money, a contract that tries to loop forever will simply run out of gas. Second, it aligns incentives: validators are compensated for the computational resources they expend, and users pay proportionally for the complexity of their transactions.

Execution Context

When the EVM executes a contract, it has access to an execution context containing information about the transaction and the current block. Solidity exposes these through global variables like msg.sender (the address that called the contract), msg.value (the ETH attached to the call), msg.data (the raw calldata), block.number, and block.timestamp. There is also tx.origin, which always refers to the original EOA that initiated the transaction, even if the contract was called by another contract. Using tx.origin for authorization is considered a security anti-pattern because it can be exploited by malicious intermediary contracts.

Reverts and Error Handling

If something goes wrong during execution, the EVM can revert. A revert undoes all state changes made during the current call (storage writes, ETH transfers, event emissions) and returns an error message to the caller. Common causes of reverts include failed require() checks, division by zero, and out-of-gas errors. The key property is atomicity: either all state changes in a transaction succeed, or none of them do. There is no partial execution. This makes smart contracts much easier to reason about compared to traditional systems where a failure mid-operation can leave data in an inconsistent state.

To learn about how smart contracts are deployed and structured, see Ethereum Smart Contracts. To understand the full journey of a transaction through the network, see Sending Transactions on Ethereum.