Code storage¶
Content-addressed code storage and the Sail-side JUMPDEST analysis.
Non-normative
This page documents the model's host interface — internal contracts of the executable specification, not protocol rules.
val code_db_lookup¶
The stored code for a content hash, or EMPTY_CODE when not witnessed. Nonempty hashes cannot identify empty code, so length zero is an unambiguous miss sentinel at this boundary.
Existential executable-code value whose concrete byte address and length
remain correlated inside CodeFields.
type Code = {
'off 'len,
code_region_valid_range('off, 'len) & code_valid_length('len).
CodeFields('off, 'len)
}The common digest type used by trie, code, and block hashes.
type hash = b256val code_region_from_input¶
Copies immutable input bytes into the executable-code arena and returns their stable arena span.
val code_region_from_input = impure { c: "code_region_from_input" } : StatelessInputSlice -> CodeRegionSliceA code-region range with its coordinate and length packed existentially.
type CodeRegionSlice = {
'off 'len,
code_region_valid_range('off, 'len).
CodeRegionSliceFields('off, 'len)
}A stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}val code_region_from_memory¶
Copies active-frame memory into the executable-code arena.
val code_region_from_memory = impure { c: "code_region_from_memory" } : EvmMemorySlice -> CodeRegionSliceA code-region range with its coordinate and length packed existentially.
type CodeRegionSlice = {
'off 'len,
code_region_valid_range('off, 'len).
CodeRegionSliceFields('off, 'len)
}An EVM-memory range with its coordinate and length packed existentially.
type EvmMemorySlice = {
'off 'len,
memory_region_valid_range('off, 'len).
EvmMemorySliceFields('off, 'len)
}val code_region_from_output¶
Copies frozen initcode output into the executable-code arena.
val code_region_from_output = impure { c: "code_region_from_output" } : OutputSlice -> CodeRegionSliceA code-region range with its coordinate and length packed existentially.
type CodeRegionSlice = {
'off 'len,
code_region_valid_range('off, 'len).
CodeRegionSliceFields('off, 'len)
}A frame-output range with its coordinate and length packed existentially.
type OutputSlice = {
'off 'len,
output_region_valid_range('off, 'len).
OutputSliceFields('off, 'len)
}val code_region_from_delegation¶
Materializes an EIP-7702 delegation designator in the executable-code arena so it follows the ordinary analysis and content-addressing path.
val code_region_from_delegation = impure { c: "code_region_from_delegation" } : address -> CodeRegionSliceA code-region range with its coordinate and length packed existentially.
type CodeRegionSlice = {
'off 'len,
code_region_valid_range('off, 'len).
CodeRegionSliceFields('off, 'len)
}A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)val jumpdest_table_alloc¶
Allocates a JUMPDEST bitmap large enough for a code body.
val jumpdest_table_alloc = impure { c: "jumpdest_table_alloc" } : CodeSlice -> jump_table_indexA source-backed executable byte span. Its length carries the separate representation invariant needed by program-counter arithmetic; this is not a protocol deployment-size limit.
type CodeSlice = {
'off 'len,
code_region_valid_range('off, 'len) & code_valid_length('len).
CodeRegionSliceFields('off, 'len)
}A reference to a code's completed JUMPDEST analysis; zero denotes
the empty bitmap.
type jump_table_index = range(0, 2 ^ 64 - 1)val jumpdest_table_mark¶
Marks one opcode-aligned program counter in an allocated JUMPDEST table.
val jumpdest_table_mark = impure { c: "jumpdest_table_mark" } : (jump_table_index, code_length, code_pointer) -> boolA contract-code length.
type code_length = range(0, code_region_bound)An absolute byte position in the code arena.
type code_pointer = range(0, code_region_bound)A reference to a code's completed JUMPDEST analysis; zero denotes
the empty bitmap.
type jump_table_index = range(0, 2 ^ 64 - 1)val code_db_store¶
Stores code with its precomputed JUMPDEST bitmap under its
KECCAK-256 hash and returns that hash.
Existential executable-code value whose concrete byte address and length
remain correlated inside CodeFields.
type Code = {
'off 'len,
code_region_valid_range('off, 'len) & code_valid_length('len).
CodeFields('off, 'len)
}The common digest type used by trie, code, and block hashes.
type hash = b256val jumpdest_ref_contains¶
Whether the referenced bitmap marks the given program counter as a
valid JUMPDEST.
val jumpdest_ref_contains = impure { c: "jumpdest_ref_contains" } : (jump_table_index, code_length, code_pointer) -> boolA contract-code length.
type code_length = range(0, code_region_bound)An absolute byte position in the code arena.
type code_pointer = range(0, code_region_bound)A reference to a code's completed JUMPDEST analysis; zero denotes
the empty bitmap.
type jump_table_index = range(0, 2 ^ 64 - 1)val code_db_read_delegation¶
Reads a stored delegation designator: the leading bit of the 168-bit result flags a well-formed designator and carries the delegate address.
val code_db_read_delegation = impure { c: "code_db_read_delegation" } : hash -> AddressResultA host address operation's success flag and address result.
struct AddressResult = {
success : bool,
address : address,
}The common digest type used by trie, code, and block hashes.
type hash = b256function analyze_code_from¶
function analyze_code_from(code, fork, table, pc) = {
let code_len = code.len;
var scanning : bool = true;
var position : code_scan_position = pc;
while scanning & position < code_len termination_measure(code_len - position) do {
let current = position;
let opcode = slice_byte(code, current);
if opcode == 0x5b then {
let marked = jumpdest_table_mark(table, code_len, current);
assert(marked, "JUMPDEST mark")
};
let opcode_value = unsigned(opcode);
let step : range(1, 33) =
if (96 <= opcode_value) & (opcode_value <= 127)
then opcode_value - 94
else if fork >= Amsterdam then {
let operation = deep_stack_operation(opcode_value);
let immediate = slice_byte(code, current + 1);
let immediate_valid = deep_stack_operation_immediate_valid(operation, immediate);
if immediate_valid then {
2
} else {
1
}
} else {
1
};
if step < code_len - current then {
position = current + step
} else {
scanning = false
}
}
}function analyze_code_from(code, fork, table, pc) = {
let code_len = code.len;
var scanning : bool = true;
var position : code_scan_position = pc;
while scanning & position < code_len termination_measure(code_len - position) do {
let current = position;
let opcode = slice_byte(code, current);
if opcode == 0x5b then {
let marked = jumpdest_table_mark(table, code_len, current);
assert(marked, "JUMPDEST mark")
};
let opcode_value = unsigned(opcode);
let step : range(1, 33) =
if (96 <= opcode_value) & (opcode_value <= 127)
then opcode_value - 94
else if fork >= Amsterdam then {
let operation = deep_stack_operation(opcode_value);
let immediate = slice_byte(code, current + 1);
let immediate_valid = deep_stack_operation_immediate_valid(operation, immediate);
if immediate_valid then {
2
} else {
1
}
} else {
1
};
if step < code_len - current then {
position = current + step
} else {
scanning = false
}
}
}Classifies an opcode against Amsterdam's immediate deep-stack operations; every other opcode maps to the non-member.
function deep_stack_operation(opcode : opcode) -> DeepStackOperation =
match opcode {
230 => DeepStackDuplicate,
231 => DeepStackSwap,
232 => DeepStackExchange,
_ => NotDeepStackOperation,
}Applies the immediate-validity rule selected by a decoded deep-stack
operation. DUPN and SWAPN share the single-index encoding, while
EXCHANGE uses the pair encoding.
function deep_stack_operation_immediate_valid(operation : DeepStackOperation, immediate : byte) -> bool =
match operation {
DeepStackDuplicate => deep_stack_immediate_valid(immediate),
DeepStackSwap => deep_stack_immediate_valid(immediate),
DeepStackExchange => exchange_immediate_valid(immediate),
NotDeepStackOperation => false,
}Marks one opcode-aligned program counter in an allocated JUMPDEST table.
val jumpdest_table_mark = impure { c: "jumpdest_table_mark" } : (jump_table_index, code_length, code_pointer) -> boolconverts 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)EIP-7954 code/initcode size bump (65536/131072).
let Amsterdam : int(amsterdam_fork_value) = sizeof(amsterdam_fork_value)An opcode-aligned scan cursor: a code position that still carries the PUSH32 immediate headroom, so reading past the current opcode stays in the code region.
type code_scan_position = range(0, code_region_bound - 32)An EVM instruction byte.
type opcode = range(0, 255)function analyze_code¶
The PUSH-aware JUMPDEST analysis (YP §9.4.3): PUSH immediate bytes
are data even when they contain 0x5b. The completed bitmap remains a
first-class Sail value; the host never scans opcodes.
function analyze_code(code : CodeSlice, fork : Fork) -> jump_table_index =
if code.len == 0 then {
EMPTY_JUMP_TABLE
} else {
let table = jumpdest_table_alloc(code);
assert(table != EMPTY_JUMP_TABLE, "JUMPDEST table allocation");
analyze_code_from(code, fork, table, 0);
table
}function analyze_code_from(code, fork, table, pc) = {
let code_len = code.len;
var scanning : bool = true;
var position : code_scan_position = pc;
while scanning & position < code_len termination_measure(code_len - position) do {
let current = position;
let opcode = slice_byte(code, current);
if opcode == 0x5b then {
let marked = jumpdest_table_mark(table, code_len, current);
assert(marked, "JUMPDEST mark")
};
let opcode_value = unsigned(opcode);
let step : range(1, 33) =
if (96 <= opcode_value) & (opcode_value <= 127)
then opcode_value - 94
else if fork >= Amsterdam then {
let operation = deep_stack_operation(opcode_value);
let immediate = slice_byte(code, current + 1);
let immediate_valid = deep_stack_operation_immediate_valid(operation, immediate);
if immediate_valid then {
2
} else {
1
}
} else {
1
};
if step < code_len - current then {
position = current + step
} else {
scanning = false
}
}
}Allocates a JUMPDEST bitmap large enough for a code body.
val jumpdest_table_alloc = impure { c: "jumpdest_table_alloc" } : CodeSlice -> jump_table_indexlet EMPTY_JUMP_TABLE : jump_table_index = 0A source-backed executable byte span. Its length carries the separate representation invariant needed by program-counter arithmetic; this is not a protocol deployment-size limit.
type CodeSlice = {
'off 'len,
code_region_valid_range('off, 'len) & code_valid_length('len).
CodeRegionSliceFields('off, 'len)
}Every supported protocol and schema fork, in activation order. This is the
sole fork identity in the model: the decoded schema byte selects a
ProtocolProfile, which stores one of these values. The bounded semantic
type prevents values outside the supported fork sequence, while each named
constant retains its precise singleton type for dependent profile typing.
type Fork = range(0, 16)A reference to a code's completed JUMPDEST analysis; zero denotes
the empty bitmap.
type jump_table_index = range(0, 2 ^ 64 - 1)function code_db_insert¶
Analyzes and stores code, returning its content hash.
function code_db_insert(code : CodeSlice, fork : Fork) -> hash = {
let jumpdest_table = analyze_code(code, fork);
let analyzed = analyzed_code(code, jumpdest_table);
code_db_store(analyzed)
}The PUSH-aware JUMPDEST analysis (YP §9.4.3): PUSH immediate bytes
are data even when they contain 0x5b. The completed bitmap remains a
first-class Sail value; the host never scans opcodes.
function analyze_code(code : CodeSlice, fork : Fork) -> jump_table_index =
if code.len == 0 then {
EMPTY_JUMP_TABLE
} else {
let table = jumpdest_table_alloc(code);
assert(table != EMPTY_JUMP_TABLE, "JUMPDEST table allocation");
analyze_code_from(code, fork, table, 0);
table
}function analyzed_code(bytes, jumpdests) =
struct { bytes = bytes.bytes, len = bytes.len, jumpdests = jumpdests }Stores code with its precomputed JUMPDEST bitmap under its
KECCAK-256 hash and returns that hash.
val code_db_store = impure { c: "code_db_store_indexed" } : Code -> hashA source-backed executable byte span. Its length carries the separate representation invariant needed by program-counter arithmetic; this is not a protocol deployment-size limit.
type CodeSlice = {
'off 'len,
code_region_valid_range('off, 'len) & code_valid_length('len).
CodeRegionSliceFields('off, 'len)
}Every supported protocol and schema fork, in activation order. This is the
sole fork identity in the model: the decoded schema byte selects a
ProtocolProfile, which stores one of these values. The bounded semantic
type prevents values outside the supported fork sequence, while each named
constant retains its precise singleton type for dependent profile typing.
type Fork = range(0, 16)The common digest type used by trie, code, and block hashes.
type hash = b256function code_db_intern_input¶
Normalizes stateless-input code into the code arena before analysis.
function code_db_intern_input(bytes : StatelessInputSlice) -> CodeSlice = {
let region = code_region_from_input(bytes);
validated_code_slice(region)
}Copies immutable input bytes into the executable-code arena and returns their stable arena span.
val code_region_from_input = impure { c: "code_region_from_input" } : StatelessInputSlice -> CodeRegionSliceConverts a source span whose producer guarantees executable cursor headroom. The explicit check re-establishes the proof after the length has crossed a non-dependent host boundary.
function validated_code_slice(bytes : CodeRegionSlice) -> CodeSlice =
if bytes.len <= sizeof(code_region_bound) - 32 then {
code_slice(bytes)
} else {
assert(false, "executable code cursor headroom");
code_slice(EMPTY_CODE_REGION_SLICE)
}A source-backed executable byte span. Its length carries the separate representation invariant needed by program-counter arithmetic; this is not a protocol deployment-size limit.
type CodeSlice = {
'off 'len,
code_region_valid_range('off, 'len) & code_valid_length('len).
CodeRegionSliceFields('off, 'len)
}A stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}function code_db_intern_memory¶
Normalizes memory-backed initcode into the code arena before analysis.
function code_db_intern_memory(bytes : EvmMemorySlice) -> CodeSlice = {
let region = code_region_from_memory(bytes);
validated_code_slice(region)
}Copies active-frame memory into the executable-code arena.
val code_region_from_memory = impure { c: "code_region_from_memory" } : EvmMemorySlice -> CodeRegionSliceConverts a source span whose producer guarantees executable cursor headroom. The explicit check re-establishes the proof after the length has crossed a non-dependent host boundary.
function validated_code_slice(bytes : CodeRegionSlice) -> CodeSlice =
if bytes.len <= sizeof(code_region_bound) - 32 then {
code_slice(bytes)
} else {
assert(false, "executable code cursor headroom");
code_slice(EMPTY_CODE_REGION_SLICE)
}A source-backed executable byte span. Its length carries the separate representation invariant needed by program-counter arithmetic; this is not a protocol deployment-size limit.
type CodeSlice = {
'off 'len,
code_region_valid_range('off, 'len) & code_valid_length('len).
CodeRegionSliceFields('off, 'len)
}An EVM-memory range with its coordinate and length packed existentially.
type EvmMemorySlice = {
'off 'len,
memory_region_valid_range('off, 'len).
EvmMemorySliceFields('off, 'len)
}function code_db_intern_output¶
Normalizes frozen creation output into the code arena before deployment.
function code_db_intern_output(bytes : OutputSlice) -> CodeSlice = {
let region = code_region_from_output(bytes);
validated_code_slice(region)
}Copies frozen initcode output into the executable-code arena.
val code_region_from_output = impure { c: "code_region_from_output" } : OutputSlice -> CodeRegionSliceConverts a source span whose producer guarantees executable cursor headroom. The explicit check re-establishes the proof after the length has crossed a non-dependent host boundary.
function validated_code_slice(bytes : CodeRegionSlice) -> CodeSlice =
if bytes.len <= sizeof(code_region_bound) - 32 then {
code_slice(bytes)
} else {
assert(false, "executable code cursor headroom");
code_slice(EMPTY_CODE_REGION_SLICE)
}A source-backed executable byte span. Its length carries the separate representation invariant needed by program-counter arithmetic; this is not a protocol deployment-size limit.
type CodeSlice = {
'off 'len,
code_region_valid_range('off, 'len) & code_valid_length('len).
CodeRegionSliceFields('off, 'len)
}A frame-output range with its coordinate and length packed existentially.
type OutputSlice = {
'off 'len,
output_region_valid_range('off, 'len).
OutputSliceFields('off, 'len)
}function code_db_resolve¶
The code for a code hash; KECCAK_EMPTY resolves to empty code, and
an unwitnessed hash is a deficient witness.
function code_db_resolve(code_hash : hash) -> Code =
if code_hash == KECCAK_EMPTY then {
EMPTY_CODE
} else {
let code = code_db_lookup(code_hash);
if code.len == 0 then {
fatal_error(WitnessDeficient)
} else {
code
}
}The stored code for a content hash, or EMPTY_CODE when not witnessed. Nonempty hashes cannot identify empty code, so length zero is an unambiguous miss sentinel at this boundary.
val code_db_lookup = impure { c: "code_db_lookup" } : hash -> Codefunction fatal_error(_reason) = exit(())let EMPTY_CODE : Code = analyzed_code(EMPTY_CODE_SLICE, EMPTY_JUMP_TABLE)keccak256 of the empty string: the codeHash of every codeless
account.
let KECCAK_EMPTY : hash = hash_from_bits(0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470)Existential executable-code value whose concrete byte address and length
remain correlated inside CodeFields.
type Code = {
'off 'len,
code_region_valid_range('off, 'len) & code_valid_length('len).
CodeFields('off, 'len)
}The reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}The common digest type used by trie, code, and block hashes.
type hash = b256