The Ethereum Virtual Machine (EVM)
The EVM is a deterministic, quasi-Turing-complete state machine that executes bytecode on every Ethereum full node. It is the computation layer of Ethereum � defining exactly how account state transitions happen in response to transactions.
EVM Architecture
- Stack-based VM: The EVM uses a 256-bit word stack (max 1024 elements). All operations act on the top of the stack. Contract bytecode is a sequence of 1-byte opcodes plus their arguments
- Word size: 256 bits (32 bytes) � chosen to align with Keccak-256 and ECC key sizes. All stack items, storage slots, and memory words are 256-bit
- Memory: Volatile byte array, expanded as needed. Cost increases quadratically with size accessed (memory expansion). Cleared between calls
- Storage: Persistent key-value store per contract (32-byte key ? 32-byte value). Stored in Ethereum's Patricia-Merkle Trie. Most expensive operation (~20,000 gas for new slot)
- Execution context: msg.sender, msg.value, block.timestamp, block.number, tx.origin are available as environment opcodes
- Determinism: Every node runs the same bytecode with the same inputs and arrives at the same state � consensus requires this
EVM Execution Simulation
class EVM:
"""Simplified 256-bit stack-based EVM"""
MAX_UINT256 = 2**256 - 1
def __init__(self, storage: dict = None, gas_limit: int = 100000):
self.stack: list = []
self.memory: bytearray = bytearray()
self.storage: dict = storage or {}
self.gas = gas_limit
self.pc = 0 # Program counter
def _pop(self) -> int:
if not self.stack:
raise Exception("Stack underflow")
return self.stack.pop()
def _push(self, val: int):
val = val & self.MAX_UINT256 # 256-bit wrap
self.stack.append(val)
def _charge_gas(self, amount: int):
self.gas -= amount
if self.gas < 0:
raise Exception(f"Out of gas! Over by {-self.gas}")
def run(self, bytecode: list) -> dict:
"""Execute a simplified bytecode sequence"""
while self.pc < len(bytecode):
opcode = bytecode[self.pc]
self.pc += 1
if opcode == "PUSH": # PUSH next value
val = bytecode[self.pc]; self.pc += 1
self._push(val); self._charge_gas(3)
print(f" PUSH {val} -> Stack: {self.stack}")
elif opcode == "ADD":
a, b = self._pop(), self._pop()
result = (a + b) & self.MAX_UINT256
self._push(result); self._charge_gas(3)
print(f" ADD {a} + {b} = {result} -> Stack: {self.stack}")
elif opcode == "MUL":
a, b = self._pop(), self._pop()
self._push((a * b) & self.MAX_UINT256); self._charge_gas(5)
elif opcode == "SSTORE": # Store to persistent storage
key, val = self._pop(), self._pop()
is_new = key not in self.storage
self.storage[key] = val
gas = 20000 if is_new else 5000
self._charge_gas(gas)
print(f" SSTORE[{key}] = {val} (cost: {gas} gas)")
elif opcode == "SLOAD": # Load from persistent storage
key = self._pop()
val = self.storage.get(key, 0)
self._push(val); self._charge_gas(800)
print(f" SLOAD[{key}] = {val}")
elif opcode == "RETURN":
size, offset = self._pop(), self._pop()
print(f" RETURN: offset={offset}, size={size}")
break
return {"storage": self.storage, "gas_used": 100000 - self.gas, "stack": self.stack}
# Execute a simple counter increment: storage[0] = storage[0] + 1
evm = EVM(storage={0: 5}, gas_limit=100000)
bytecode = [
"PUSH", 0, # Push storage key 0
"SLOAD", # Load storage[0] -> 5
"PUSH", 1, # Push increment value 1
"ADD", # 5 + 1 = 6
"PUSH", 0, # Push storage key 0
"SSTORE", # storage[0] = 6
]
result = evm.run(bytecode)
print(f"\nResult: gas_used={result['gas_used']}, storage={result['storage']}")Common Mistakes
- Thinking EVM computation is free once you deploy a contract � every EVM opcode has a gas cost that users must pay. Storage is the most expensive operation (SSTORE: 20,000 gas for new slots). Poorly optimized contracts can cost users 10-100x more than necessary
- Assuming 256-bit integers don't overflow � Solidity's uint256 wraps at 2^256. Integer overflow was a common vulnerability before Solidity 0.8 added overflow checking by default. Always use SafeMath or Solidity 0.8+ for arithmetic
Tip
Tip
Practice The Ethereum Virtual Machine EVM in small, isolated examples before integrating into larger projects. Breaking concepts into small experiments builds genuine understanding faster than reading alone.
Each block references the previous block's hash, forming an immutable chain
Practice Task
Note
Practice Task — (1) Write a working example of The Ethereum Virtual Machine EVM from scratch without looking at notes. (2) Modify it to handle an edge case (empty input, null value, or error state). (3) Share your solution in the Priygop community for feedback.
Quick Quiz
Common Mistake
Warning
A common mistake with The Ethereum Virtual Machine EVM is skipping edge case testing — empty inputs, null values, and unexpected data types. Always validate boundary conditions to write robust, production-ready blockchain code.
Key Takeaways
- The EVM is a deterministic, quasi-Turing-complete state machine that executes bytecode on every Ethereum full node.
- Stack-based VM: The EVM uses a 256-bit word stack (max 1024 elements). All operations act on the top of the stack. Contract bytecode is a sequence of 1-byte opcodes plus their arguments
- Word size: 256 bits (32 bytes) � chosen to align with Keccak-256 and ECC key sizes. All stack items, storage slots, and memory words are 256-bit
- Memory: Volatile byte array, expanded as needed. Cost increases quadratically with size accessed (memory expansion). Cleared between calls