Writing Solidity without understanding the EVM is like writing C without understanding memory. You produce code that works — until it doesn't, and you won't know why. The EVM has unusual properties that directly affect how you write, optimize, and secure contracts.
The Ethereum Virtual Machine is a stack-based, 256-bit word virtual
machine that executes compiled bytecode stored at contract addresses
on the Ethereum state trie.
Stack-based:
Operations read/write a stack of 256-bit words. Max depth: 1024.
Exceeding causes Stack Overflow and reverts execution.
256-bit word size:
All EVM operations work on 32-byte words. uint256, bytes32,
and address (padded to 32 bytes) are native types. Smaller types
(uint8, uint128) occupy 32 bytes in memory/stack — only in
storage do they pack efficiently.
Deterministic:
Same input state + same transaction = same output. Always.
No randomness, no external I/O, no floating point.
Gas-metered:
Every opcode has a fixed gas cost. Execution halts at 0 gas,
reverting all state changes but consuming the gas budget.
Isolated:
Each contract runs in its own context. Cross-contract
communication requires explicit CALL opcodes..sol → Parser → AST → Type Checker → Yul IR → [Optimizer] → EVM Bytecode + ABI
Optimizer (--optimize-runs N):
N=200: default, balanced deployment vs. runtime cost.
N=1: minimize deployment gas (contracts called rarely).
N=1000000: minimize runtime gas (DEX hot paths).Creation bytecode (initcode):
Runs once at deployment. Executes constructor. Returns runtime
bytecode. Discarded after. No size limit.
Runtime bytecode:
Lives at the contract address permanently. Executes on every call.
Subject to the 24KB EIP-170 limit.
If you hit 24KB: split contracts, use Diamond (EIP-2535), or optimize.STORAGE — persistent, most expensive:
On-chain forever. 2^256 × 32-byte slots.
SLOAD cold: 2100 gas | SLOAD warm: 100 gas
SSTORE new slot: 20,000 gas | SSTORE update: 2,900–5,000 gas
Declare at contract level: uint256 public totalSupply;
MEMORY — temporary, moderate:
Exists only during one call. Cleared on return.
Quadratic cost growth past ~724 words.
uint256[] memory arr = new uint256[](100);
CALLDATA — read-only, cheapest:
Raw input data. Cannot be modified.
function foo(uint256[] calldata data) external { ... }
STACK — implicit, limited:
256-bit words. Max 1024 depth. Local value-type variables.
uint256 x = 5; // lives on stack automaticallyOperation Gas Cost ADD, SUB 3 MUL, DIV 5 SLOAD (cold) 2,100 SLOAD (warm, same tx) 100 SSTORE (new slot) 20,000 SSTORE (update) 2,900–5,000 KECCAK256 30 + 6 per word LOG (event base) 375 + 375/topic + 8/data byte CREATE 32,000 + init code CALL (external) 700 base + callee gas Rule: storage touches are expensive. Arithmetic is cheap. External calls add callee's entire execution cost to yours.
curl -L https://foundry.paradigm.xyz | bash && foundryup forge init my-project && cd my-project forge build # compile forge test # run tests forge fmt # format code foundry.toml — key settings: [profile.default] optimizer = true optimizer_runs = 200 solc = "0.8.20"
Solidity compiles to EVM bytecode that runs on a 256-bit stack machine. Every operation costs gas. Data lives in four locations with very different cost profiles. The 24KB bytecode limit constrains large contracts. Understanding the compilation pipeline helps you reason about gas and optimization.
Module 1: SOLIDITY FUNDAMENTALS
The Language, the Machine, and the Mental Model
Major events are on Discord
Join for live sessions, announcements, and event rooms while you learn.
Join Discord →