Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
Disassembling small contracts trains the muscle that lets you verify what your compiler actually produced — useful for security review, optimizer-bug hunting, and reasoning about gas without trusting forge snapshot blindly. After a few hours of practice, simple contracts become as readable as the Solidity that generated them.
Disassemble the runtime bytecode of a 2-line contract using evm disasm.
forge inspect <Contract> deployedBytecode and pipe to evm disasm (geth tool). Locate the function dispatcher: it ends with multiple SHR/EQ/JUMPI sequences, one per function.PUSH1 0x2A. Confirm it appears in the body of answer().PUSH1 0x80 PUSH1 0x40 MSTORE.Use these three in order. Each builds on the one before.
In one paragraph, explain what 'bytecode' and 'disassembly' mean in the context of an EVM contract.
Walk me through what a Solidity function dispatcher looks like in bytecode — how the selector check works, and what happens when the selector doesn't match.
Given an unverified contract on Etherscan, what's a practical workflow for figuring out what its key functions do using only the bytecode plus on-chain transaction traces?
// Source:
// contract C { function answer() external pure returns (uint) { return 42; } }
//
// Runtime bytecode (from `forge inspect C deployedBytecode`):
// 0x6080604052348015...
PUSH1 0x80 // 0x6080
PUSH1 0x40 // 0x6040
MSTORE // store 0x80 at memory[0x40] (free mem ptr)
CALLVALUE // 0x34 — push msg.value
DUP1
ISZERO // require(msg.value == 0)
PUSH2 0x000F
JUMPI // jump if zero
PUSH1 0x00
DUP1
REVERT // payable check failed
JUMPDEST // 0x000F
POP
CALLDATASIZE // dispatcher
PUSH1 0x04
LT
PUSH2 0x0035
JUMPI // if calldatasize < 4, jump to fallback
... // selector check, then PUSH1 0x2A (=42), RETURN