RLP encoding¶
Recursive Length Prefix (Yellow Paper Appendix B), the canonical serialization Ethereum uses for accounts, block headers, transactions, receipts, and Merkle-Patricia trie nodes. RLP encodes a recursive structure of byte strings and lists; it imposes no type semantics, only a minimal length-prefixed framing.
A byte < 0x80 is its own encoding; strings of length n ≤ 55 use
0x80 + n; longer strings use 0xb7 + |len| followed by the big-endian
length. Lists mirror this with the 0xc0/0xf7 prefix bands over an
already-encoded payload. Integers encode as their minimal big-endian byte
string with no leading zeros. Decoding proceeds one level at a time; a
field reference retains the complete encoded item as a source slice and its
content length without copying; the content is always the corresponding
suffix.
Malformed RLP terminates validation with fatal_error(RlpDecode) at the
point where the invalid encoding is detected.
Constants¶
RLP's short-form threshold and the fixed encoded widths used throughout the execution-layer data model.
let RLP_SHORT_LENGTH_LIMIT¶
let RLP_SHORT_LENGTH_LIMIT : int(55) = 55let RLP_WORD_LENGTH_LIMIT¶
let RLP_WORD_LENGTH_LIMIT : int(32) = WORD_BYTE_LENGTHlet WORD_BYTE_LENGTH : int(32) = 32let RLP_UINT64_LENGTH_LIMIT¶
let RLP_UINT64_LENGTH_LIMIT : int(8) = EIGHT_BYTE_LENGTHlet EIGHT_BYTE_LENGTH : int(8) = 8let RLP_ENCODED_WORD_LENGTH¶
let RLP_ENCODED_WORD_LENGTH : int(33) = 33let RLP_ENCODED_ADDRESS_LENGTH¶
let RLP_ENCODED_ADDRESS_LENGTH : int(21) = 21type rlp_scratch_length¶
An RLP byte count that can be materialized in the scratch arena. The host scratch-region limit is enforced while recursive collection totals are accumulated.
type rlp_scratch_length = range(0, scratch_region_bound)Executor scratch-arena capacity.
type scratch_region_bound : Int = default_host_region_boundtype rlp_natural_valid¶
Values accepted by the generic natural-number RLP helpers. Canonical RLP is unbounded; optimized builds refine values entering the native encoder.
type rlp_natural_valid('value : Int) -> Bool = 0 <= 'valuetype rlp_natural_increment_valid¶
Natural values that can be incremented while counting encoded bytes.
type rlp_natural_increment_valid('value : Int) -> Bool = 0 <= 'valuetype rlp_natural_size¶
Encoded width returned by the generic natural-number RLP helper.
type rlp_natural_size = natfunction rlp_scratch_small_length¶
function rlp_scratch_small_length(value) = valuefunction rlp_scratch_small_length(value) = valuefunction rlp_scratch_length_add¶
function rlp_scratch_length_add(left, right) =
if right <= sizeof(scratch_region_bound) - left then {
left + right
} else {
fatal_error(RlpDecode)
}function fatal_error(_reason) = exit(())function rlp_scratch_length_add(left, right) =
if right <= sizeof(scratch_region_bound) - left then {
left + right
} else {
fatal_error(RlpDecode)
}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,
}Executor scratch-arena capacity.
type scratch_region_bound : Int = default_host_region_boundThe encoders¶
function rlp_nat_length_byte¶
function rlp_nat_length_byte(value : range(0, 255)) -> byte =
get_slice_int(8, value, 0)val get_slice_int = pure {_: "get_slice_int"}: forall ('w : Int). (int('w), int, int) -> bits('w)An 8-bit byte.
type byte = bits(8)function rlp_byte_length_byte¶
function rlp_byte_length_byte(value) = {
assert(value <= 255);
let length = value;
get_slice_int(8, length, 0)
}val get_slice_int = pure {_: "get_slice_int"}: forall ('w : Int). (int('w), int, int) -> bits('w)function rlp_byte_length_byte(value) = {
assert(value <= 255);
let length = value;
get_slice_int(8, length, 0)
}function rlp_length_word¶
function rlp_length_word(value) = {
word_of_nat_byte_count(value)
}function rlp_length_word(value) = {
word_of_nat_byte_count(value)
}function word_of_nat_byte_count(value) =
if value < 2 ^ 256 then {
u256(value)
} else {
assert(false);
WORD_ZERO
}function rlp_minimal_word_len¶
function rlp_minimal_word_len(w) = {
word_byte_length(w)
}function rlp_minimal_word_len(w) = {
word_byte_length(w)
}function word_byte_length(value) = {
let bit_length = word_bit_length(value);
if bit_length == 0 then {
0
} else {
tdiv_nat(bit_length + 7, 8)
}
}function rlp_natural_increment¶
function rlp_natural_increment(value) = value + 1function rlp_natural_increment(value) = value + 1function rlp_minimal_uint_len¶
Computes a natural's minimal big-endian byte length recursively.
function rlp_minimal_uint_len(n) =
if n == 0 then {
0
} else {
let remaining_length = rlp_minimal_uint_len(n / 256);
rlp_natural_increment(remaining_length)
}Computes a natural's minimal big-endian byte length recursively.
function rlp_minimal_uint_len(n) =
if n == 0 then {
0
} else {
let remaining_length = rlp_minimal_uint_len(n / 256);
rlp_natural_increment(remaining_length)
}function rlp_natural_increment(value) = value + 1function rlp_length_prefix_len¶
function rlp_length_prefix_len(len) =
if len <= RLP_SHORT_LENGTH_LIMIT then {
1
} else {
let length_word = rlp_length_word(len);
1 + rlp_minimal_word_len(length_word)
}function rlp_length_prefix_len(len) =
if len <= RLP_SHORT_LENGTH_LIMIT then {
1
} else {
let length_word = rlp_length_word(len);
1 + rlp_minimal_word_len(length_word)
}function rlp_length_word(value) = {
word_of_nat_byte_count(value)
}function rlp_minimal_word_len(w) = {
word_byte_length(w)
}let RLP_SHORT_LENGTH_LIMIT : int(55) = 55function rlp_string_size¶
function rlp_string_size(len, first) = {
if (len == 1) & (first[7] == bitzero) then {
1
} else {
rlp_length_prefix_len(len) + len
}
}function rlp_length_prefix_len(len) =
if len <= RLP_SHORT_LENGTH_LIMIT then {
1
} else {
let length_word = rlp_length_word(len);
1 + rlp_minimal_word_len(length_word)
}function rlp_string_size(len, first) = {
if (len == 1) & (first[7] == bitzero) then {
1
} else {
rlp_length_prefix_len(len) + len
}
}function rlp_input_slice_size¶
function rlp_input_slice_size(data) = {
let len = data.len;
let first =
if len == 0 then 0x00 else slice_byte(data, 0);
rlp_string_size(len, first)
}function rlp_input_slice_size(data) = {
let len = data.len;
let first =
if len == 0 then 0x00 else slice_byte(data, 0);
rlp_string_size(len, first)
}function rlp_string_size(len, first) = {
if (len == 1) & (first[7] == bitzero) then {
1
} else {
rlp_length_prefix_len(len) + len
}
}function rlp_materialized_slice_size¶
Returns the materializable RLP width of a byte slice after its caller has selected the backing region and loaded the first byte.
function rlp_materialized_slice_size(length : rlp_scratch_length, first : byte) -> rlp_scratch_length =
if (length == 1) & (first[7] == bitzero) then {
1
} else {
let prefix_size = rlp_length_prefix_len(length);
let prefix_length = rlp_scratch_small_length(prefix_size);
rlp_scratch_length_add(length, prefix_length)
}function rlp_length_prefix_len(len) =
if len <= RLP_SHORT_LENGTH_LIMIT then {
1
} else {
let length_word = rlp_length_word(len);
1 + rlp_minimal_word_len(length_word)
}function rlp_scratch_length_add(left, right) =
if right <= sizeof(scratch_region_bound) - left then {
left + right
} else {
fatal_error(RlpDecode)
}function rlp_scratch_small_length(value) = valueAn 8-bit byte.
type byte = bits(8)An RLP byte count that can be materialized in the scratch arena. The host scratch-region limit is enforced while recursive collection totals are accumulated.
type rlp_scratch_length = range(0, scratch_region_bound)function rlp_scratch_region_size¶
Returns the materializable RLP width of a scratch-backed byte slice.
function rlp_scratch_region_size(data : ScratchSlice) -> rlp_scratch_length = {
let first =
if data.len == 0 then 0x00 else slice_byte(data, 0);
rlp_materialized_slice_size(data.len, first)
}Returns the materializable RLP width of a byte slice after its caller has selected the backing region and loaded the first byte.
function rlp_materialized_slice_size(length : rlp_scratch_length, first : byte) -> rlp_scratch_length =
if (length == 1) & (first[7] == bitzero) then {
1
} else {
let prefix_size = rlp_length_prefix_len(length);
let prefix_length = rlp_scratch_small_length(prefix_size);
rlp_scratch_length_add(length, prefix_length)
}A scratch-arena range with its coordinate and length packed existentially.
type ScratchSlice = {
'off 'len,
scratch_valid_range('off, 'len).
ScratchSliceFields('off, 'len)
}An RLP byte count that can be materialized in the scratch arena. The host scratch-region limit is enforced while recursive collection totals are accumulated.
type rlp_scratch_length = range(0, scratch_region_bound)function rlp_log_data_size¶
Returns the materializable RLP width of a retained log-data slice.
function rlp_log_data_size(data : LogDataSlice) -> rlp_scratch_length = {
let first =
if data.len == 0 then 0x00 else slice_byte(data, 0);
rlp_materialized_slice_size(data.len, first)
}Returns the materializable RLP width of a byte slice after its caller has selected the backing region and loaded the first byte.
function rlp_materialized_slice_size(length : rlp_scratch_length, first : byte) -> rlp_scratch_length =
if (length == 1) & (first[7] == bitzero) then {
1
} else {
let prefix_size = rlp_length_prefix_len(length);
let prefix_length = rlp_scratch_small_length(prefix_size);
rlp_scratch_length_add(length, prefix_length)
}A log-data range with its coordinate and length packed existentially.
type LogDataSlice = {
'off 'len,
log_data_valid_range('off, 'len).
LogDataSliceFields('off, 'len)
}An RLP byte count that can be materialized in the scratch arena. The host scratch-region limit is enforced while recursive collection totals are accumulated.
type rlp_scratch_length = range(0, scratch_region_bound)function rlp_uint_word_size¶
function rlp_uint_word_size(w) = {
let len = rlp_minimal_word_len(w);
let first =
if len == 1 then word_low_byte(w) else 0x00;
if (len == 1) & (first[7] == bitzero) then {
1
} else {
1 + len
}
}function rlp_minimal_word_len(w) = {
word_byte_length(w)
}function rlp_uint_word_size(w) = {
let len = rlp_minimal_word_len(w);
let first =
if len == 1 then word_low_byte(w) else 0x00;
if (len == 1) & (first[7] == bitzero) then {
1
} else {
1 + len
}
}function word_low_byte(value) = get_slice_int(8, value, 0)function rlp_uint_nat_size¶
function rlp_uint_nat_size(n) = {
if n < sizeof(word_modulus) then {
let word = u256(n);
rlp_uint_word_size(word)
} else {
/* n >= 2^256 implies a minimal encoding of at least 33 bytes, so the
* single-byte RLP case is unreachable on this branch. */
let len = rlp_minimal_uint_len(n);
if len <= RLP_SHORT_LENGTH_LIMIT then {
1 + len
} else {
1 + rlp_minimal_uint_len(len) + len
}
}
}Computes a natural's minimal big-endian byte length recursively.
function rlp_minimal_uint_len(n) =
if n == 0 then {
0
} else {
let remaining_length = rlp_minimal_uint_len(n / 256);
rlp_natural_increment(remaining_length)
}function rlp_uint_nat_size(n) = {
if n < sizeof(word_modulus) then {
let word = u256(n);
rlp_uint_word_size(word)
} else {
/* n >= 2^256 implies a minimal encoding of at least 33 bytes, so the
* single-byte RLP case is unreachable on this branch. */
let len = rlp_minimal_uint_len(n);
if len <= RLP_SHORT_LENGTH_LIMIT then {
1 + len
} else {
1 + rlp_minimal_uint_len(len) + len
}
}
}function rlp_uint_word_size(w) = {
let len = rlp_minimal_word_len(w);
let first =
if len == 1 then word_low_byte(w) else 0x00;
if (len == 1) & (first[7] == bitzero) then {
1
} else {
1 + len
}
}function u256(value) = valuelet RLP_SHORT_LENGTH_LIMIT : int(55) = 55The 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)The type-level modulus of EVM-word arithmetic.
type word_modulus : Int = 2 ^ 256function rlp_uint_u64_size¶
function rlp_uint_u64_size(n) =
if n < 2 ^ 7 then {
1
} else if n < 2 ^ 8 then {
2
} else if n < 2 ^ 16 then {
3
} else if n < 2 ^ 24 then {
4
} else if n < 2 ^ 32 then {
5
} else if n < 2 ^ 40 then {
6
} else if n < 2 ^ 48 then {
7
} else if n < 2 ^ 56 then {
8
} else {
9
}We have special support for raising values to the power of two. Any Sail expression 2 ^ x will be compiled to this builtin.
val pow2 = pure {lean: "_lean_pow2i", _: "pow2"}: forall ('n : Int). int('n) -> int(2 ^ 'n)function rlp_uint_u64_size(n) =
if n < 2 ^ 7 then {
1
} else if n < 2 ^ 8 then {
2
} else if n < 2 ^ 16 then {
3
} else if n < 2 ^ 24 then {
4
} else if n < 2 ^ 32 then {
5
} else if n < 2 ^ 40 then {
6
} else if n < 2 ^ 48 then {
7
} else if n < 2 ^ 56 then {
8
} else {
9
}function rlp_word_size¶
function rlp_word_size() -> int(33) = RLP_ENCODED_WORD_LENGTHlet RLP_ENCODED_WORD_LENGTH : int(33) = 33function rlp_addr_size¶
function rlp_addr_size() -> int(21) = RLP_ENCODED_ADDRESS_LENGTHlet RLP_ENCODED_ADDRESS_LENGTH : int(21) = 21function rlp_list_size¶
function rlp_list_size(content_len) = {
rlp_length_prefix_len(content_len) + content_len
}function rlp_length_prefix_len(len) =
if len <= RLP_SHORT_LENGTH_LIMIT then {
1
} else {
let length_word = rlp_length_word(len);
1 + rlp_minimal_word_len(length_word)
}function rlp_list_size(content_len) = {
rlp_length_prefix_len(content_len) + content_len
}function rlp_scratch_list_size¶
Adds the canonical list prefix to a materializable RLP content length.
function rlp_scratch_list_size(content_len : rlp_scratch_length) -> rlp_scratch_length = {
let prefix_size = rlp_length_prefix_len(content_len);
let prefix_length = rlp_scratch_small_length(prefix_size);
rlp_scratch_length_add(content_len, prefix_length)
}function rlp_length_prefix_len(len) =
if len <= RLP_SHORT_LENGTH_LIMIT then {
1
} else {
let length_word = rlp_length_word(len);
1 + rlp_minimal_word_len(length_word)
}function rlp_scratch_length_add(left, right) =
if right <= sizeof(scratch_region_bound) - left then {
left + right
} else {
fatal_error(RlpDecode)
}function rlp_scratch_small_length(value) = valueAn RLP byte count that can be materialized in the scratch arena. The host scratch-region limit is enforced while recursive collection totals are accumulated.
type rlp_scratch_length = range(0, scratch_region_bound)function rlp_input_scratch_slice_size¶
Returns the materializable RLP width of a source-backed byte slice.
function rlp_input_scratch_slice_size(data : StatelessInputSlice) -> rlp_scratch_length = {
let first =
if data.len == 0 then 0x00 else slice_byte(data, 0);
rlp_materialized_slice_size(data.len, first)
}Returns the materializable RLP width of a byte slice after its caller has selected the backing region and loaded the first byte.
function rlp_materialized_slice_size(length : rlp_scratch_length, first : byte) -> rlp_scratch_length =
if (length == 1) & (first[7] == bitzero) then {
1
} else {
let prefix_size = rlp_length_prefix_len(length);
let prefix_length = rlp_scratch_small_length(prefix_size);
rlp_scratch_length_add(length, prefix_length)
}A stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}An RLP byte count that can be materialized in the scratch arena. The host scratch-region limit is enforced while recursive collection totals are accumulated.
type rlp_scratch_length = range(0, scratch_region_bound)function rlp_scratch_scratch_slice_size¶
function rlp_scratch_scratch_slice_size(data : ScratchSlice) -> rlp_scratch_length =
rlp_scratch_region_size(data)Returns the materializable RLP width of a scratch-backed byte slice.
function rlp_scratch_region_size(data : ScratchSlice) -> rlp_scratch_length = {
let first =
if data.len == 0 then 0x00 else slice_byte(data, 0);
rlp_materialized_slice_size(data.len, first)
}A scratch-arena range with its coordinate and length packed existentially.
type ScratchSlice = {
'off 'len,
scratch_valid_range('off, 'len).
ScratchSliceFields('off, 'len)
}An RLP byte count that can be materialized in the scratch arena. The host scratch-region limit is enforced while recursive collection totals are accumulated.
type rlp_scratch_length = range(0, scratch_region_bound)function rlp_log_scratch_slice_size¶
Sizes a retained log-data slice for the materializable overload.
function rlp_log_scratch_slice_size(data : LogDataSlice) -> rlp_scratch_length =
rlp_log_data_size(data)Returns the materializable RLP width of a retained log-data slice.
function rlp_log_data_size(data : LogDataSlice) -> rlp_scratch_length = {
let first =
if data.len == 0 then 0x00 else slice_byte(data, 0);
rlp_materialized_slice_size(data.len, first)
}A log-data range with its coordinate and length packed existentially.
type LogDataSlice = {
'off 'len,
log_data_valid_range('off, 'len).
LogDataSliceFields('off, 'len)
}An RLP byte count that can be materialized in the scratch arena. The host scratch-region limit is enforced while recursive collection totals are accumulated.
type rlp_scratch_length = range(0, scratch_region_bound)function rlp_write_string_prefix¶
function rlp_write_string_prefix(len, first) = {
if (len == 1) & (first[7] == bitzero) then {
return ()
};
if len <= RLP_SHORT_LENGTH_LIMIT then {
let length_byte = rlp_length_byte(len);
let prefix = add_bits(0x80, length_byte);
scratch_push_byte(prefix)
} else {
let length_word = rlp_length_word(len);
let length_len = rlp_minimal_word_len(length_word);
let length_byte = rlp_length_byte(length_len);
let prefix = add_bits(0xb7, length_byte);
scratch_push_byte(prefix);
scratch_push_word_be(length_word, length_len)
}
}val add_bits = pure {ocaml: "add_vec", interpreter: "add_vec", lem: "add_vec", coq: "add_vec", lean: "_lean_add", _: "add_bits"}: forall ('n : Int).
(bits('n), bits('n)) -> bits('n)function rlp_length_word(value) = {
word_of_nat_byte_count(value)
}function rlp_minimal_word_len(w) = {
word_byte_length(w)
}function rlp_write_string_prefix(len, first) = {
if (len == 1) & (first[7] == bitzero) then {
return ()
};
if len <= RLP_SHORT_LENGTH_LIMIT then {
let length_byte = rlp_length_byte(len);
let prefix = add_bits(0x80, length_byte);
scratch_push_byte(prefix)
} else {
let length_word = rlp_length_word(len);
let length_len = rlp_minimal_word_len(length_word);
let length_byte = rlp_length_byte(length_len);
let prefix = add_bits(0xb7, length_byte);
scratch_push_byte(prefix);
scratch_push_word_be(length_word, length_len)
}
}Appends one byte without constructing a Sail list.
function scratch_push_byte(data : byte) -> unit = {
let arena = scratch_arena;
scratch_arena = host_scratch_store_byte(arena.len, data)
}Appends the low len bytes of a word in canonical big-endian order.
function scratch_push_word_be(data : word, len : range(0, 32)) -> unit = {
if len != 0 then {
let arena = scratch_arena;
scratch_arena = host_scratch_store_word(arena.len, data, len)
}
}let RLP_SHORT_LENGTH_LIMIT : int(55) = 55function rlp_write_list_prefix¶
function rlp_write_list_prefix(content_len) = {
if content_len <= RLP_SHORT_LENGTH_LIMIT then {
let length_byte = rlp_length_byte(content_len);
let prefix = add_bits(0xc0, length_byte);
scratch_push_byte(prefix)
} else {
let length_word = rlp_length_word(content_len);
let length_len = rlp_minimal_word_len(length_word);
let length_byte = rlp_length_byte(length_len);
let prefix = add_bits(0xf7, length_byte);
scratch_push_byte(prefix);
scratch_push_word_be(length_word, length_len)
}
}val add_bits = pure {ocaml: "add_vec", interpreter: "add_vec", lem: "add_vec", coq: "add_vec", lean: "_lean_add", _: "add_bits"}: forall ('n : Int).
(bits('n), bits('n)) -> bits('n)function rlp_length_word(value) = {
word_of_nat_byte_count(value)
}function rlp_minimal_word_len(w) = {
word_byte_length(w)
}function rlp_write_list_prefix(content_len) = {
if content_len <= RLP_SHORT_LENGTH_LIMIT then {
let length_byte = rlp_length_byte(content_len);
let prefix = add_bits(0xc0, length_byte);
scratch_push_byte(prefix)
} else {
let length_word = rlp_length_word(content_len);
let length_len = rlp_minimal_word_len(length_word);
let length_byte = rlp_length_byte(length_len);
let prefix = add_bits(0xf7, length_byte);
scratch_push_byte(prefix);
scratch_push_word_be(length_word, length_len)
}
}Appends one byte without constructing a Sail list.
function scratch_push_byte(data : byte) -> unit = {
let arena = scratch_arena;
scratch_arena = host_scratch_store_byte(arena.len, data)
}Appends the low len bytes of a word in canonical big-endian order.
function scratch_push_word_be(data : word, len : range(0, 32)) -> unit = {
if len != 0 then {
let arena = scratch_arena;
scratch_arena = host_scratch_store_word(arena.len, data, len)
}
}let RLP_SHORT_LENGTH_LIMIT : int(55) = 55function rlp_write_input_slice¶
Appends a source-backed byte slice as an RLP string.
function rlp_write_input_slice(data : StatelessInputSlice) -> unit = {
let first =
if data.len == 0 then 0x00 else slice_byte(data, 0);
rlp_write_string_prefix(data.len, first);
scratch_push_slice(data)
}function rlp_write_string_prefix(len, first) = {
if (len == 1) & (first[7] == bitzero) then {
return ()
};
if len <= RLP_SHORT_LENGTH_LIMIT then {
let length_byte = rlp_length_byte(len);
let prefix = add_bits(0x80, length_byte);
scratch_push_byte(prefix)
} else {
let length_word = rlp_length_word(len);
let length_len = rlp_minimal_word_len(length_word);
let length_byte = rlp_length_byte(length_len);
let prefix = add_bits(0xb7, length_byte);
scratch_push_byte(prefix);
scratch_push_word_be(length_word, length_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 rlp_write_scratch_slice¶
Appends a scratch-backed byte slice as an RLP string.
function rlp_write_scratch_slice(data : ScratchSlice) -> unit = {
let first =
if data.len == 0 then 0x00 else slice_byte(data, 0);
rlp_write_string_prefix(data.len, first);
scratch_push_slice(data)
}function rlp_write_string_prefix(len, first) = {
if (len == 1) & (first[7] == bitzero) then {
return ()
};
if len <= RLP_SHORT_LENGTH_LIMIT then {
let length_byte = rlp_length_byte(len);
let prefix = add_bits(0x80, length_byte);
scratch_push_byte(prefix)
} else {
let length_word = rlp_length_word(len);
let length_len = rlp_minimal_word_len(length_word);
let length_byte = rlp_length_byte(length_len);
let prefix = add_bits(0xb7, length_byte);
scratch_push_byte(prefix);
scratch_push_word_be(length_word, length_len)
}
}A scratch-arena range with its coordinate and length packed existentially.
type ScratchSlice = {
'off 'len,
scratch_valid_range('off, 'len).
ScratchSliceFields('off, 'len)
}function rlp_write_log_data_slice¶
Appends a retained log-data slice as an RLP string.
function rlp_write_log_data_slice(data : LogDataSlice) -> unit = {
let first =
if data.len == 0 then 0x00 else slice_byte(data, 0);
rlp_write_string_prefix(data.len, first);
scratch_push_slice(data)
}function rlp_write_string_prefix(len, first) = {
if (len == 1) & (first[7] == bitzero) then {
return ()
};
if len <= RLP_SHORT_LENGTH_LIMIT then {
let length_byte = rlp_length_byte(len);
let prefix = add_bits(0x80, length_byte);
scratch_push_byte(prefix)
} else {
let length_word = rlp_length_word(len);
let length_len = rlp_minimal_word_len(length_word);
let length_byte = rlp_length_byte(length_len);
let prefix = add_bits(0xb7, length_byte);
scratch_push_byte(prefix);
scratch_push_word_be(length_word, length_len)
}
}A log-data range with its coordinate and length packed existentially.
type LogDataSlice = {
'off 'len,
log_data_valid_range('off, 'len).
LogDataSliceFields('off, 'len)
}function rlp_write_uint_word¶
function rlp_write_uint_word(w) = {
let len = rlp_minimal_word_len(w);
let first =
if len == 1 then word_low_byte(w) else 0x00;
rlp_write_string_prefix(len, first);
let word = u256(w);
scratch_push_word_be(word, len)
}function rlp_minimal_word_len(w) = {
word_byte_length(w)
}function rlp_write_string_prefix(len, first) = {
if (len == 1) & (first[7] == bitzero) then {
return ()
};
if len <= RLP_SHORT_LENGTH_LIMIT then {
let length_byte = rlp_length_byte(len);
let prefix = add_bits(0x80, length_byte);
scratch_push_byte(prefix)
} else {
let length_word = rlp_length_word(len);
let length_len = rlp_minimal_word_len(length_word);
let length_byte = rlp_length_byte(length_len);
let prefix = add_bits(0xb7, length_byte);
scratch_push_byte(prefix);
scratch_push_word_be(length_word, length_len)
}
}function rlp_write_uint_word(w) = {
let len = rlp_minimal_word_len(w);
let first =
if len == 1 then word_low_byte(w) else 0x00;
rlp_write_string_prefix(len, first);
let word = u256(w);
scratch_push_word_be(word, len)
}Appends the low len bytes of a word in canonical big-endian order.
function scratch_push_word_be(data : word, len : range(0, 32)) -> unit = {
if len != 0 then {
let arena = scratch_arena;
scratch_arena = host_scratch_store_word(arena.len, data, len)
}
}function u256(value) = valuefunction word_low_byte(value) = get_slice_int(8, value, 0)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 rlp_write_uint_nat¶
function rlp_write_uint_nat(n) =
if n < sizeof(word_modulus) then {
let word = u256(n);
rlp_write_uint_word(word)
} else {
fatal_error(InvalidConfig)
}function fatal_error(_reason) = exit(())function rlp_write_uint_nat(n) =
if n < sizeof(word_modulus) then {
let word = u256(n);
rlp_write_uint_word(word)
} else {
fatal_error(InvalidConfig)
}function rlp_write_uint_word(w) = {
let len = rlp_minimal_word_len(w);
let first =
if len == 1 then word_low_byte(w) else 0x00;
rlp_write_string_prefix(len, first);
let word = u256(w);
scratch_push_word_be(word, len)
}function u256(value) = valueThe 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 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)The type-level modulus of EVM-word arithmetic.
type word_modulus : Int = 2 ^ 256function rlp_write_uint_u64¶
function rlp_write_uint_u64(n) = {
let word = u256(n);
rlp_write_uint_word(word)
}function rlp_write_uint_u64(n) = {
let word = u256(n);
rlp_write_uint_word(word)
}function rlp_write_uint_word(w) = {
let len = rlp_minimal_word_len(w);
let first =
if len == 1 then word_low_byte(w) else 0x00;
rlp_write_string_prefix(len, first);
let word = u256(w);
scratch_push_word_be(word, len)
}function u256(value) = valueThe 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 rlp_write_word¶
Appends one full-width EVM word as an RLP byte string.
function rlp_write_word(w : word) -> unit = {
rlp_write_string_prefix(WORD_BYTE_LENGTH, 0x00);
scratch_push_word_be(w, WORD_BYTE_LENGTH)
}function rlp_write_string_prefix(len, first) = {
if (len == 1) & (first[7] == bitzero) then {
return ()
};
if len <= RLP_SHORT_LENGTH_LIMIT then {
let length_byte = rlp_length_byte(len);
let prefix = add_bits(0x80, length_byte);
scratch_push_byte(prefix)
} else {
let length_word = rlp_length_word(len);
let length_len = rlp_minimal_word_len(length_word);
let length_byte = rlp_length_byte(length_len);
let prefix = add_bits(0xb7, length_byte);
scratch_push_byte(prefix);
scratch_push_word_be(length_word, length_len)
}
}Appends the low len bytes of a word in canonical big-endian order.
function scratch_push_word_be(data : word, len : range(0, 32)) -> unit = {
if len != 0 then {
let arena = scratch_arena;
scratch_arena = host_scratch_store_word(arena.len, data, len)
}
}let WORD_BYTE_LENGTH : int(32) = 32The 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 rlp_write_addr¶
Appends one Ethereum address as an RLP byte string.
function rlp_write_addr(a : address) -> unit = {
rlp_write_string_prefix(ADDRESS_BYTE_LENGTH, a[0]);
scratch_push_address(a)
}function rlp_write_string_prefix(len, first) = {
if (len == 1) & (first[7] == bitzero) then {
return ()
};
if len <= RLP_SHORT_LENGTH_LIMIT then {
let length_byte = rlp_length_byte(len);
let prefix = add_bits(0x80, length_byte);
scratch_push_byte(prefix)
} else {
let length_word = rlp_length_word(len);
let length_len = rlp_minimal_word_len(length_word);
let length_byte = rlp_length_byte(length_len);
let prefix = add_bits(0xb7, length_byte);
scratch_push_byte(prefix);
scratch_push_word_be(length_word, length_len)
}
}Appends a fixed 20-byte address at the cursor.
function scratch_push_address(data : address) -> unit = {
let arena = scratch_arena;
scratch_arena = host_scratch_store_address(arena.len, data)
}let ADDRESS_BYTE_LENGTH : int(20) = 20A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)The scratch encoder¶
An encoder reserves its complete output before the first write. The expected length is checked once at the boundary, keeping allocation and scratch lifetime out of domain callers while leaving primitive writes direct and allocation-free.
type RlpEncoder¶
One exact-size RLP construction in the shared scratch arena.
struct RlpEncoder = {
start : source_pointer,
expected_len : source_length,
}A byte length or regular-layout count derived from a source region.
type source_length = range(0, default_host_region_bound)An absolute byte position in a named source region.
type source_pointer = range(0, default_host_region_bound)function rlp_encoder_begin¶
function rlp_encoder_begin(expected_len) =
struct { start = scratch_reserve(expected_len), expected_len = expected_len }function rlp_encoder_begin(expected_len) =
struct { start = scratch_reserve(expected_len), expected_len = expected_len }function scratch_reserve(len) = {
let arena = scratch_arena;
let reserved = host_scratch_reserve(arena.len, len);
assert(reserved, "scratch reserve");
arena.len
}function rlp_encoder_finish¶
Finishes an exact-size construction and retains its encoded bytes.
function rlp_encoder_finish(encoder : RlpEncoder) -> ScratchSlice = {
let encoded = scratch_finish(encoder.start);
assert(encoded.len == encoder.expected_len, "RLP encoder length");
encoded
}The slice covering everything pushed since start.
function scratch_finish(start : source_pointer) -> ScratchSlice =
let start_offset = start in
let arena = scratch_arena in
let stop_offset = arena.len in
if start_offset <= stop_offset then {
sub_slice(arena, start, stop_offset - start_offset)
} else {
assert(false, "scratch finish mark");
EMPTY_SCRATCH_SLICE
}One exact-size RLP construction in the shared scratch arena.
struct RlpEncoder = {
start : source_pointer,
expected_len : source_length,
}A scratch-arena range with its coordinate and length packed existentially.
type ScratchSlice = {
'off 'len,
scratch_valid_range('off, 'len).
ScratchSliceFields('off, 'len)
}function rlp_encoder_rewind¶
Releases every byte appended by an encoder after its result is consumed.
function rlp_encoder_rewind(encoder : RlpEncoder) -> unit =
scratch_rewind(encoder.start)Discards everything pushed since mark.
function scratch_rewind(mark : source_pointer) -> unit =
let mark_offset = mark in
let arena = scratch_arena in
let cursor_offset = arena.len in
if mark_offset <= cursor_offset then {
scratch_arena = sub_slice(arena, 0, mark);
host_scratch_truncate(mark)
} else {
assert(false, "scratch rewind mark")
}One exact-size RLP construction in the shared scratch arena.
struct RlpEncoder = {
start : source_pointer,
expected_len : source_length,
}