The instruction set¶
The abstract syntax of EVM bytecode: the full defined opcode set through
the Osaka fork (Yellow Paper Appendix H, plus the EIPs that extend it โ
PUSH0 EIP-3855, SHL/SHR/SAR EIP-145, CLZ EIP-7939,
TLOAD/TSTORE EIP-1153, MCOPY EIP-5656, BLOBHASH/BLOBBASEFEE
EIP-4844/EIP-7516, and DUPN/SWAPN/EXCHANGE EIP-8024).
type ast¶
One constructor per opcode. Immediates are carried inline: PUSH
holds its byte width (0โ32) and value, DUP/SWAP hold the index
n, LOG holds its topic count. The constructor groups are labelled
with the opcode byte range they cover. Decoding code bytes into this
AST is fetch's job; undefined bytes decode to INVALID and halt
exceptionally when executed.
union ast = {
/* 0x00, 0x0b: arithmetic */
STOP : unit, ADD : unit, MUL : unit, SUB : unit, DIV : unit,
SDIV : unit, MOD : unit, SMOD : unit, ADDMOD : unit,
MULMOD : unit, EXP : unit, SIGNEXTEND : unit,
/* 0x10, 0x1e: comparison / bitwise */
LT : unit, GT : unit, SLT : unit, SGT : unit, EQ : unit,
ISZERO : unit, AND : unit, OR : unit, XOR : unit, NOT : unit,
BYTE : unit, SHL : unit, SHR : unit, SAR : unit, CLZ : unit,
/* 0x20: keccak */
KECCAK256 : unit,
/* 0x30, 0x3f: environment / account */
ADDRESS : unit, BALANCE : unit, ORIGIN : unit, CALLER : unit,
CALLVALUE : unit, CALLDATALOAD : unit, CALLDATASIZE : unit,
CALLDATACOPY : unit, CODESIZE : unit, CODECOPY : unit,
GASPRICE : unit, EXTCODESIZE : unit, EXTCODECOPY : unit,
RETURNDATASIZE : unit, RETURNDATACOPY : unit, EXTCODEHASH : unit,
/* 0x40, 0x4a: block */
BLOCKHASH : unit, COINBASE : unit, TIMESTAMP : unit, NUMBER : unit,
PREVRANDAO : unit, GASLIMIT : unit, CHAINID : unit,
SELFBALANCE : unit, BASEFEE : unit, BLOBHASH : unit, BLOBBASEFEE : unit,
/* EIP-7843 (0x4b) */
SLOTNUM : unit,
/* 0x50, 0x5e: stack / memory / storage / flow */
POP : unit, MLOAD : unit, MSTORE : unit, MSTORE8 : unit,
SLOAD : unit, SSTORE : unit, JUMP : unit, JUMPI : unit,
PC : unit, MSIZE : unit, GAS : unit, JUMPDEST : unit,
TLOAD : unit, TSTORE : unit, MCOPY : unit,
/* 0x5f, 0x7f: push (width 0..32, value) */
PUSH : (push_width, word),
/* 0x80, 0x9f: dup / swap (n) */
DUP : stack_operation_index, SWAP : stack_operation_index,
/* 0xa0, 0xa4: log (num topics) */
LOG : log_topic_count,
/* 0xe6, 0xe8: EIP-8024 deep-stack access (immediate byte) */
DUPN : byte, SWAPN : byte, EXCHANGE : byte,
/* 0xf0, 0xff: system */
opcode_CREATE : unit, CALL : unit, CALLCODE : unit, RETURN : unit,
DELEGATECALL : unit, CREATE2 : unit, STATICCALL : unit,
REVERT : unit, INVALID : unit, SELFDESTRUCT : unit
}An 8-bit byte.
type byte = bits(8)The number of indexed topics attached to one log.
type log_topic_count = range(0, 4)The immediate-byte width of a PUSH instruction.
type push_width = range(0, 32)A nonzero operand-stack position used by DUP and SWAP.
type stack_operation_index = range(1, 16)The EVM 256-bit machine word (YP ยง9.1). A transparent range keeps the mathematical subtype relation visible: narrower non-negative ranges can be passed as words without a model-level conversion.
type word = range(0, 2 ^ 256 - 1)function decode_single_stack_index¶
Decodes the immediate shared by EIP-8024 DUPN and SWAPN into
their one-based deep-stack index (17โ235).
function decode_single_stack_index(immediate : byte) -> deep_stack_index = {
let valid = deep_stack_immediate_valid(immediate);
assert(valid);
let value : opcode = unsigned(immediate);
if value <= 90 then {
value + 145
} else {
assert(128 <= value);
value - 111
}
}Whether an EIP-8024 DUPN/SWAPN immediate is valid. Invalid
immediates remain opcode-aligned during JUMPDEST analysis.
function deep_stack_immediate_valid(immediate : byte) -> bool = {
let value : opcode = unsigned(immediate);
value <= 90 | 128 <= value
}converts a bit vector of length $n$ to an integer in the range $0$ to $2^n - 1$.
val unsigned = pure {ocaml: "uint", lem: "uint", interpreter: "uint", coq: "uint", lean: "BitVec.toNatInt", _: "sail_unsigned"}: forall ('n : Int).
bits('n) -> range(0, 2 ^ 'n - 1)An 8-bit byte.
type byte = bits(8)The one-based deep-stack index decoded by EIP-8024 DUPN and SWAPN.
type deep_stack_index = range(17, 235)An EVM instruction byte.
type opcode = range(0, 255)function decode_exchange_stack_indices¶
Decodes the EIP-8024 EXCHANGE immediate into the two zero-based
stack depths that it exchanges.
function decode_exchange_stack_indices(immediate : byte) -> (stack_index, stack_index) = {
let valid = exchange_immediate_valid(immediate);
assert(valid);
let shifted : byte = xor_vec(immediate, 0x8f);
let quotient : range(0, 15) = unsigned(shifted[7 .. 4]);
let remainder : range(0, 15) = unsigned(shifted[3 .. 0]);
if quotient < remainder then {
(quotient + 1, remainder + 1)
} else {
(remainder + 1, 29 - quotient)
}
}Whether an EIP-8024 EXCHANGE immediate is valid. Invalid immediates
remain opcode-aligned during JUMPDEST analysis.
function exchange_immediate_valid(immediate : byte) -> bool = {
let value : opcode = unsigned(immediate);
value <= 81 | 128 <= value
}converts a bit vector of length $n$ to an integer in the range $0$ to $2^n - 1$.
val unsigned = pure {ocaml: "uint", lem: "uint", interpreter: "uint", coq: "uint", lean: "BitVec.toNatInt", _: "sail_unsigned"}: forall ('n : Int).
bits('n) -> range(0, 2 ^ 'n - 1)val xor_vec = pure {lem: "xor_vec", coq: "xor_vec", ocaml: "xor_vec", interpreter: "xor_vec", lean: "_lean_bvxor", _: "xor_bits"}: forall ('n : Int).
(bits('n), bits('n)) -> bits('n)An 8-bit byte.
type byte = bits(8)A zero-based index from the top of the operand stack.
type stack_index = range(0, 1023)