Smart Contracts: EVM Internals
The Magical Factory Inside Ethereum
Imagine a giant factory that sits inside every computer running Ethereum. This factory is called the EVM — the Ethereum Virtual Machine.
When you send a smart contract to Ethereum, the factory reads your instructions and does exactly what you asked. No shortcuts. No cheating. Every factory on every computer runs the same steps and gets the same answer.
Let’s explore how this magical factory works inside!
EVM Architecture: The Factory Blueprint
What Is the EVM?
Think of the EVM like a vending machine that everyone in the world shares.
- You put in a coin (pay gas)
- You press buttons (send a transaction)
- The machine follows its program exactly
- Out comes your result (new contract state)
┌─────────────────────────────┐
│ Your Transaction │
└─────────────┬───────────────┘
▼
┌─────────────────────────────┐
│ EVM (The Factory) │
│ ┌───────┐ ┌───────────┐ │
│ │ Stack │ │ Memory │ │
│ └───────┘ └───────────┘ │
│ ┌───────────────────────┐ │
│ │ Storage │ │
│ └───────────────────────┘ │
└─────────────┬───────────────┘
▼
┌─────────────────────────────┐
│ Updated Blockchain State │
└─────────────────────────────┘
Key Parts of the Factory
- Program Counter — Points to the current instruction
- Stack — Where math happens (like a calculator)
- Memory — Temporary scratch paper
- Storage — Permanent filing cabinet
- Gas Counter — Tracks how much fuel is left
Simple Example:
Your code: x = 5 + 3
EVM does:
1. Push 5 onto stack
2. Push 3 onto stack
3. Run ADD opcode
4. Stack now has 8
Opcodes: The Factory Commands
What Are Opcodes?
Opcodes are tiny instruction cards that tell the EVM what to do.
Think of them like LEGO instructions:
- Step 1: Pick up red brick
- Step 2: Attach to blue brick
- Step 3: Done!
Each opcode is just one byte — a number from 0 to 255.
Common Opcodes You’ll See
| Opcode | Name | What It Does |
|---|---|---|
0x01 |
ADD | Adds two numbers |
0x02 |
MUL | Multiplies two numbers |
0x52 |
MSTORE | Saves to memory |
0x55 |
SSTORE | Saves to storage |
0x56 |
JUMP | Jumps to new instruction |
0xF3 |
RETURN | Sends back result |
Example: Adding Numbers
// Solidity: uint result = 2 + 3;
// EVM opcodes:
PUSH1 0x02 // Push 2 onto stack
PUSH1 0x03 // Push 3 onto stack
ADD // Pop both, push 5
Real Life: It’s like a recipe card that says “Add 2 eggs, add 3 cups flour, mix together.”
Stack-Based Execution: The Calculator
Why a Stack?
Imagine a stack of plates:
- You can only add plates on top
- You can only take plates from the top
- You can’t grab one from the middle!
The EVM works the same way. Numbers go on top, and operations grab from the top.
graph TD A["Push 5"] --> B["Stack: [5]"] B --> C["Push 3"] C --> D["Stack: [5, 3]"] D --> E["ADD"] E --> F["Stack: [8]"]
Stack Rules
- Maximum depth: 1,024 items
- Each item: 256 bits (32 bytes)
- Too deep? Transaction fails!
Simple Example: Multiply
Goal: Calculate 4 × 7
Step 1: PUSH 4 Stack: [4]
Step 2: PUSH 7 Stack: [4, 7]
Step 3: MUL Stack: [28]
Answer is on top: 28
Why This Matters: The stack is simple but fast. No need for named variables — just push, pop, and compute!
Memory vs Storage vs Calldata
Three Types of “Paper”
Think of your smart contract like a student at school:
| Type | Like… | Lifespan | Cost |
|---|---|---|---|
| Memory | Scratch paper | Gone after class | Cheap |
| Storage | Your locker | Stays forever | Expensive |
| Calldata | Test question sheet | Read-only input | Cheapest |
graph TD A["Transaction Input"] --> B["Calldata<br/>Read-only"] B --> C["Memory<br/>Scratch work"] C --> D["Storage<br/>Saved forever"]
Memory: Your Scratch Paper
- Created fresh every function call
- Byte-addressable — you pick exact spots
- Cheap to use but grows in cost
function example() public {
// Memory: temporary array
uint[] memory temp = new uint[](10);
temp[0] = 42; // Gone when function ends
}
Storage: Your Permanent Locker
- Lives on the blockchain forever
- Very expensive — costs real gas
- Key-value pairs — like a filing cabinet
contract Locker {
uint public saved = 100; // Storage!
function update() public {
saved = 200; // Costs ~20,000 gas
}
}
Calldata: The Input Sheet
- Read-only — you can’t change it
- Cheapest option for function inputs
- Only for external functions
function process(
bytes calldata data // Cheapest!
) external {
// Can read but not modify data
}
Gas Cost Comparison
╔══════════════╦═══════════════╗
║ Operation ║ Gas Cost ║
╠══════════════╬═══════════════╣
║ Calldata ║ ~3 gas/byte ║
║ Memory ║ ~3 gas/word ║
║ Storage ║ ~20,000 gas ║
╚══════════════╩═══════════════╝
Bytecode: The Machine Language
What Is Bytecode?
When you write Solidity, the compiler turns your code into bytecode — the language the EVM actually reads.
Think of it like translating English to robot language:
- You write:
x = 1 + 2 - Compiler makes:
60016002016000
How Bytecode Works
Human-Readable Hex Bytecode
───────────────────────────────────
PUSH1 0x02 → 6002
PUSH1 0x03 → 6003
ADD → 01
All pushed together: 600260030100
Example: Tiny Contract
// Solidity
contract Tiny {
function add() public pure
returns (uint) {
return 2 + 3;
}
}
Bytecode snippet:
6002 // PUSH1 2
6003 // PUSH1 3
01 // ADD
Two Types of Bytecode
- Creation bytecode — Runs once to deploy
- Runtime bytecode — Stays on chain forever
graph TD A["Solidity Code"] --> B["Compiler"] B --> C["Creation Bytecode"] C --> D["Deploy to Chain"] D --> E["Runtime Bytecode<br/>Lives on blockchain"]
Contract Size Limits
The 24KB Rule
Every smart contract has a size limit: 24,576 bytes (24 KB).
Why? Imagine if contracts could be infinitely large:
- Block gas limits would break
- Nodes would struggle to sync
- Ethereum would slow down
What Counts Toward the Limit?
- All your compiled bytecode
- Constructor code (only at deploy time)
- Runtime code (stays forever)
Tips to Stay Under the Limit
| Problem | Solution |
|---|---|
| Too many functions | Split into multiple contracts |
| Long error messages | Use error codes |
| Duplicate code | Create libraries |
| Big constants | Store off-chain |
Example: Contract Too Big!
// ❌ This might be too big
contract Everything {
function a() {}
function b() {}
// ... 200 more functions
}
// ✅ Split it up!
contract PartA {
function a() {}
}
contract PartB {
function b() {}
}
The EIP-170 Story
In 2016, Ethereum added this limit. Before that, someone could deploy a huge contract and crash the network!
Contract Size Check:
───────────────────
If bytecode > 24,576 bytes
→ Transaction FAILS
→ No deployment
→ Gas refunded
Putting It All Together
How a Transaction Flows
graph TD A["You Send Transaction"] --> B["EVM Loads Bytecode"] B --> C["Reads Opcodes One by One"] C --> D["Uses Stack for Math"] D --> E["Memory for Temp Work"] E --> F["Storage for Permanent Data"] F --> G["Returns Result"]
Quick Summary
| Concept | One-Liner |
|---|---|
| EVM | The shared computer running contracts |
| Opcodes | Tiny instruction cards |
| Stack | Calculator that uses plates |
| Memory | Scratch paper (temporary) |
| Storage | Filing cabinet (permanent) |
| Calldata | Input sheet (read-only) |
| Bytecode | Robot language from Solidity |
| 24KB Limit | Max contract size |
You Did It!
You now understand how the EVM works inside:
- The architecture is like a factory with specific parts
- Opcodes are simple instruction cards
- The stack is a plate pile for calculations
- Memory, storage, and calldata are three types of paper
- Bytecode is the machine language
- 24KB is the maximum contract size
Next time you deploy a contract, you’ll know exactly what’s happening under the hood!
Think of the EVM as a very honest robot. It does exactly what the opcodes say — nothing more, nothing less. That’s what makes smart contracts trustworthy!
