Solia saysThe EVM has four memory namespaces: a 1024-deep stack for compute, scratch byte-addressable memory that vanishes per call, persistent 256-bit-keyed storage on-chain, and read-only calldata from the caller.
Every Ethereum execution touches four namespaces. The stack holds up to 1024 256-bit words and is where arithmetic happens. Memory is a byte-array that grows on demand, costs gas quadratically as it expands, and dies when the call returns. Storage is the only persistent state: 256-bit slots keyed by 256-bit indices, written to disk and surviving forever. Calldata is the read-only input from whoever called this contract — cheap to read, untouchable to write.
The cost ordering matches the durability ordering: stack ops are 3 gas, memory grows quadratically, storage writes are thousands of gas. Knowing which namespace to use is the first lesson in writing efficient contracts. The demo runs a tiny VM through each namespace.
Power-ups you unlock
Stack: 1024-deep, 256-bit words, where math happens
Memory: byte-array, grows on demand, dies per call
Storage: persistent 256-bit slots, the only on-chain state
Calldata: read-only input, untouchable, cheap to read
Gas cost orders: stack < memory < storage
The Reentrancy Reaper attacks — common mistakes
Putting working data in storage instead of memory (huge gas burn)
Forgetting memory dies per call — no cross-call persistence
Trying to write calldata (it is immutable)
Confusing slot 0 in storage with stack position 0
Boss battleRun a sequence of PUSH, MSTORE, SSTORE, and CALLDATALOAD operations on a tiny EVM and observe each namespace.
Example code
<!doctype html><html><head><meta charset="utf-8"></head>
<body style="background:#06040d;color:#e6e0ff;font-family:monospace;padding:20px"><pre id="o"></pre>
<script>
// tiny EVM with the four namespaces.
const evm = { stack:[], memory:{}, storage:{}, calldata:{0:'0x42', 32:'0x100'} };
const log = [];
function PUSH(v){ evm.stack.push(v); log.push('PUSH ' + v + ' → stack=[' + evm.stack.join(',') + ']'); }
function MSTORE(off, v){ evm.memory[off] = v; log.push('MSTORE @' + off + ' = ' + v + ' → memory[' + off + ']=' + v); }
function SSTORE(slot, v){ evm.storage[slot] = v; log.push('SSTORE slot ' + slot + ' ← ' + v + ' → storage[' + slot + ']=' + v + ' (persistent)'); }
function SLOAD(slot){ const v = evm.storage[slot] || 0; evm.stack.push(v); log.push('SLOAD slot ' + slot + ' → stack push ' + v); }
function CALLDATALOAD(off){ const v = evm.calldata[off] || '0'; evm.stack.push(v); log.push('CALLDATALOAD @' + off + ' → stack push ' + v + ' (read-only)'); }
PUSH(16); PUSH(32);
MSTORE(0, '0x30');
SSTORE(0, 42); SLOAD(0);
CALLDATALOAD(0);
log.push('');
log.push('storage[0] = ' + evm.storage[0] + ' (persists after call)');
log.push('memory = wiped on return calldata = read-only')
document.getElementById('o').textContent = log.join('\n');
</script></body></html>