State: logs and refunds¶
Log emission (YP §4.4.1) — including the EIP-7708 transfer and burn logs — and the gas-refund counter.
function k_log_topics¶
Appends bounded topic operands without constructing a Sail list.
function k_log_topics(topics : LogTopics) -> unit = match topics {
LogTopics0() => (),
LogTopics1(t0) => log_add_topic(t0),
LogTopics2((t0, t1)) => {
log_add_topic(t0);
log_add_topic(t1)
},
LogTopics3((t0, t1, t2)) => {
log_add_topic(t0);
log_add_topic(t1);
log_add_topic(t2)
},
LogTopics4((t0, t1, t2, t3)) => {
log_add_topic(t0);
log_add_topic(t1);
log_add_topic(t2);
log_add_topic(t3)
},
}Appends one topic to the current record.
val log_add_topic = impure { c: "log_add_topic" } : word -> unitThe bounded topic operands of one LOG0–LOG4 instruction. Keeping the
arity in the constructor avoids allocating a Sail list for at most four
stack words.
union LogTopics = {
/* `LOG0`: no topics */
LogTopics0 : unit,
/* `LOG1`: one topic */
LogTopics1 : word,
/* `LOG2`: two topics in stack-pop order */
LogTopics2 : (word, word),
/* `LOG3`: three topics in stack-pop order */
LogTopics3 : (word, word, word),
/* `LOG4`: four topics in stack-pop order */
LogTopics4 : (word, word, word, word),
}function k_log_data¶
Appends a log payload without crossing an aggregate/list glue boundary.
function k_log_data(data : LogData) -> unit = match data {
LogDataMemory(bytes) => log_add_data_memory(bytes),
LogDataWord(value) => log_add_data_word(value),
}Copies an EVM-memory payload into the current retained log record.
val log_add_data_memory = impure { c: "log_add_data_memory" } : EvmMemorySlice -> unitAppends one canonical big-endian word payload to the current record.
val log_add_data_word = impure { c: "log_add_data_word" } : word -> unitA log payload retained by the host: either an existing byte slice or the canonical big-endian bytes of one EVM word used by system logs.
union LogData = {
/* an EVM-memory payload copied into retained log storage */
LogDataMemory : EvmMemorySlice,
/* the canonical big-endian bytes of one EVM word */
LogDataWord : word,
}function k_log¶
Appends a log record (YP §4.4.1) to the transaction's log series.
function k_log(a : address, topics : LogTopics, data : LogData) -> unit = {
log_begin(a);
k_log_topics(topics);
k_log_data(data)
}Appends a log payload without crossing an aggregate/list glue boundary.
function k_log_data(data : LogData) -> unit = match data {
LogDataMemory(bytes) => log_add_data_memory(bytes),
LogDataWord(value) => log_add_data_word(value),
}Appends bounded topic operands without constructing a Sail list.
function k_log_topics(topics : LogTopics) -> unit = match topics {
LogTopics0() => (),
LogTopics1(t0) => log_add_topic(t0),
LogTopics2((t0, t1)) => {
log_add_topic(t0);
log_add_topic(t1)
},
LogTopics3((t0, t1, t2)) => {
log_add_topic(t0);
log_add_topic(t1);
log_add_topic(t2)
},
LogTopics4((t0, t1, t2, t3)) => {
log_add_topic(t0);
log_add_topic(t1);
log_add_topic(t2);
log_add_topic(t3)
},
}Begins a log record for address.
val log_begin = impure { c: "log_begin" } : address -> unitA log payload retained by the host: either an existing byte slice or the canonical big-endian bytes of one EVM word used by system logs.
union LogData = {
/* an EVM-memory payload copied into retained log storage */
LogDataMemory : EvmMemorySlice,
/* the canonical big-endian bytes of one EVM word */
LogDataWord : word,
}The bounded topic operands of one LOG0–LOG4 instruction. Keeping the
arity in the constructor avoids allocating a Sail list for at most four
stack words.
union LogTopics = {
/* `LOG0`: no topics */
LogTopics0 : unit,
/* `LOG1`: one topic */
LogTopics1 : word,
/* `LOG2`: two topics in stack-pop order */
LogTopics2 : (word, word),
/* `LOG3`: three topics in stack-pop order */
LogTopics3 : (word, word, word),
/* `LOG4`: four topics in stack-pop order */
LogTopics4 : (word, word, word, word),
}A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)function read_logs¶
Captures the current transaction's consecutive retained log range.
function read_logs() -> LogSeriesRef = {
let start = logs_tx_start();
let count = logs_tx_count();
struct { start = start, count = count }
}Number of retained logs emitted by the current transaction.
val logs_tx_count = impure { c: "logs_tx_count" } : unit -> log_store_indexStart and length of the current transaction's retained log range.
val logs_tx_start = impure { c: "logs_tx_start" } : unit -> log_store_indexA consecutive transaction-local view into the block-lifetime host log store. Reverted frame records are removed before this view is captured.
struct LogSeriesRef = {
start : log_store_index,
count : log_store_index,
}function read_log_data¶
Returns the source-backed payload of one retained log record.
function read_log_data(index : log_store_index) -> LogDataSlice = {
let off = log_data_offset(index);
let len = log_data_length(index);
if len <= sizeof(log_data_region_bound) - off then {
log_data_slice(off, len)
} else {
assert(false, "log data slice overflow");
EMPTY_LOG_DATA_SLICE
}
}A length in retained log-data storage.
type log_data_length = range(0, log_data_region_bound)Offset of an indexed retained log's payload in the host log-data arena.
val log_data_offset = impure { c: "log_data_off" } : log_store_index -> log_data_pointerfunction log_data_slice(off, len) =
struct { bytes = off, len = len }let EMPTY_LOG_DATA_SLICE : LogDataSliceFields(0, 0) = log_data_slice(0, 0)A log-data range with its coordinate and length packed existentially.
type LogDataSlice = {
'off 'len,
log_data_valid_range('off, 'len).
LogDataSliceFields('off, 'len)
}Retained transaction-log data arena capacity.
type log_data_region_bound : Int = default_host_region_boundA position in the block-lifetime host log store.
type log_store_index = range(0, log_store_index_bound)let LOGS_BLOOM_BYTE_LENGTH¶
let LOGS_BLOOM_BYTE_LENGTH : int(256) = 256function bloom_bit_mask¶
Constructs a one-hot mask for a bit within a bloom byte.
function bloom_bit_mask(bit_to_set : range(0, 7)) -> byte =
sail_shiftleft(0x01, bit_to_set)val sail_shiftleft = pure {lean: "_lean_shiftl", _: "shiftl"}: forall ('n : Int) ('amount : Int).
(bitvector('n), int('amount)) -> bitvector('n)An 8-bit byte.
type byte = bits(8)function bloom_set_bit¶
Sets one bit (0–2047) in the bloom, most-significant-byte first.
function bloom_set_bit(bloom : LogsBloom, bit_to_set : bloom_bit_index) -> LogsBloom = {
var out = bloom;
let quotient = bit_to_set / 8;
let natural_byte : range(0, 255) =
if quotient <= 255 then {
quotient
} else {
assert(false);
0
};
let remainder = tmod_int(bit_to_set, 8);
let bit_in_byte : range(0, 7) =
if remainder <= 7 then {
remainder
} else {
assert(false);
0
};
let mask = bloom_bit_mask(bit_in_byte);
out[natural_byte] = or_vec(out[natural_byte], mask);
out
}Constructs a one-hot mask for a bit within a bloom byte.
function bloom_bit_mask(bit_to_set : range(0, 7)) -> byte =
sail_shiftleft(0x01, bit_to_set)val or_vec = pure {lem: "or_vec", coq: "or_vec", ocaml: "or_vec", interpreter: "or_vec", lean: "_lean_bvor", _: "or_bits"}: forall ('n : Int).
(bits('n), bits('n)) -> bits('n)The 2048-bit logs bloom filter (YP §4.4.1), as 256 bytes.
type LogsBloom = vector(256, dec, byte)A bit position in the 2048-bit log bloom.
type bloom_bit_index = range(0, 2047)function bloom_add_entry_hash¶
Adds a hashed bloom entry: three bits from its KECCAK-256.
function bloom_add_entry_hash(bloom : LogsBloom, h : hash) -> LogsBloom = {
let bytes = h;
let first_bits = append(bytes[0][2 .. 0], bytes[1]);
let first_index = unsigned(first_bits);
var out = bloom_set_bit(bloom, first_index);
let second_bits = append(bytes[2][2 .. 0], bytes[3]);
let second_index = unsigned(second_bits);
out = bloom_set_bit(out, second_index);
let third_bits = append(bytes[4][2 .. 0], bytes[5]);
let third_index = unsigned(third_bits);
out = bloom_set_bit(out, third_index);
out
}Sets one bit (0–2047) in the bloom, most-significant-byte first.
function bloom_set_bit(bloom : LogsBloom, bit_to_set : bloom_bit_index) -> LogsBloom = {
var out = bloom;
let quotient = bit_to_set / 8;
let natural_byte : range(0, 255) =
if quotient <= 255 then {
quotient
} else {
assert(false);
0
};
let remainder = tmod_int(bit_to_set, 8);
let bit_in_byte : range(0, 7) =
if remainder <= 7 then {
remainder
} else {
assert(false);
0
};
let mask = bloom_bit_mask(bit_in_byte);
out[natural_byte] = or_vec(out[natural_byte], mask);
out
}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)The 2048-bit logs bloom filter (YP §4.4.1), as 256 bytes.
type LogsBloom = vector(256, dec, byte)The common digest type used by trie, code, and block hashes.
type hash = b256function bloom_add_log_at¶
Adds one retained log record to the bloom (YP §4.4.1, the M function).
function bloom_add_log_at(bloom : LogsBloom, index : log_store_index) -> LogsBloom = {
let address = log_address(index);
let address_hash = keccak256_address(address);
var out = bloom_add_entry_hash(bloom, address_hash);
var topic : log_store_index = 0;
let topic_count = log_topics_count(index);
while topic < topic_count termination_measure(topic_count - topic) do {
let topic_value = log_topic(index, topic);
let topic_hash = keccak256_word(topic_value);
out = bloom_add_entry_hash(out, topic_hash);
topic = log_store_index_increment(topic)
};
out
}Adds a hashed bloom entry: three bits from its KECCAK-256.
function bloom_add_entry_hash(bloom : LogsBloom, h : hash) -> LogsBloom = {
let bytes = h;
let first_bits = append(bytes[0][2 .. 0], bytes[1]);
let first_index = unsigned(first_bits);
var out = bloom_set_bit(bloom, first_index);
let second_bits = append(bytes[2][2 .. 0], bytes[3]);
let second_index = unsigned(second_bits);
out = bloom_set_bit(out, second_index);
let third_bits = append(bytes[4][2 .. 0], bytes[5]);
let third_index = unsigned(third_bits);
out = bloom_set_bit(out, third_index);
out
}KECCAK-256 of a 20-byte address (secure-trie account keys).
val keccak256_address = impure { c: "host_keccak_address" } : address -> hashKECCAK-256 of one EVM word in canonical big-endian byte order.
val keccak256_word = impure { c: "host_keccak_word" } : word -> hashIndexed access to a retained log record.
val log_address = impure { c: "log_addr" } : log_store_index -> addressAdvances a valid log cursor without fixed-width wrapping.
function log_store_index_increment(value : log_store_index) -> log_store_index =
if value < sizeof(log_store_index_bound) then {
value + 1
} else {
assert(false, "log store index overflow");
0
}Reads one topic from an indexed retained log.
val log_topic = impure { c: "log_topic" } : (log_store_index, log_store_index) -> wordNumber of topics attached to an indexed retained log.
val log_topics_count = impure { c: "log_topic_count" } : log_store_index -> log_store_indexThe 2048-bit logs bloom filter (YP §4.4.1), as 256 bytes.
type LogsBloom = vector(256, dec, byte)A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)A position in the block-lifetime host log store.
type log_store_index = range(0, log_store_index_bound)function bloom_add_logs¶
Adds a consecutive retained log range to a bloom.
function bloom_add_logs(bloom : LogsBloom, logs : LogSeriesRef) -> LogsBloom = {
var out = bloom;
var offset : log_store_index = 0;
while offset < logs.count termination_measure(logs.count - offset) do {
let index = log_store_index_add(logs.start, offset);
out = bloom_add_log_at(out, index);
offset = log_store_index_increment(offset)
};
out
}Adds one retained log record to the bloom (YP §4.4.1, the M function).
function bloom_add_log_at(bloom : LogsBloom, index : log_store_index) -> LogsBloom = {
let address = log_address(index);
let address_hash = keccak256_address(address);
var out = bloom_add_entry_hash(bloom, address_hash);
var topic : log_store_index = 0;
let topic_count = log_topics_count(index);
while topic < topic_count termination_measure(topic_count - topic) do {
let topic_value = log_topic(index, topic);
let topic_hash = keccak256_word(topic_value);
out = bloom_add_entry_hash(out, topic_hash);
topic = log_store_index_increment(topic)
};
out
}Adds a relative log offset to its series start without wrapping.
function log_store_index_add(left : log_store_index, right : log_store_index) -> log_store_index =
if right <= sizeof(log_store_index_bound) - left then {
left + right
} else {
assert(false, "log store index overflow");
0
}Advances a valid log cursor without fixed-width wrapping.
function log_store_index_increment(value : log_store_index) -> log_store_index =
if value < sizeof(log_store_index_bound) then {
value + 1
} else {
assert(false, "log store index overflow");
0
}A consecutive transaction-local view into the block-lifetime host log store. Reverted frame records are removed before this view is captured.
struct LogSeriesRef = {
start : log_store_index,
count : log_store_index,
}The 2048-bit logs bloom filter (YP §4.4.1), as 256 bytes.
type LogsBloom = vector(256, dec, byte)A position in the block-lifetime host log store.
type log_store_index = range(0, log_store_index_bound)function logs_bloom_for_logs¶
The bloom of one receipt's retained log range.
function logs_bloom_for_logs(logs : LogSeriesRef) -> LogsBloom =
bloom_add_logs(EMPTY_LOGS_BLOOM, logs)Adds a consecutive retained log range to a bloom.
function bloom_add_logs(bloom : LogsBloom, logs : LogSeriesRef) -> LogsBloom = {
var out = bloom;
var offset : log_store_index = 0;
while offset < logs.count termination_measure(logs.count - offset) do {
let index = log_store_index_add(logs.start, offset);
out = bloom_add_log_at(out, index);
offset = log_store_index_increment(offset)
};
out
}let EMPTY_LOGS_BLOOM : LogsBloom = vector_init(256, 0x00)A consecutive transaction-local view into the block-lifetime host log store. Reverted frame records are removed before this view is captured.
struct LogSeriesRef = {
start : log_store_index,
count : log_store_index,
}The 2048-bit logs bloom filter (YP §4.4.1), as 256 bytes.
type LogsBloom = vector(256, dec, byte)function logs_bloom_or¶
Combines two receipt blooms for the block header.
function logs_bloom_or(left : LogsBloom, right : LogsBloom) -> LogsBloom = {
var out = left;
foreach (k from 0 to 255) {
out[k] = or_vec(out[k], right[k])
};
out
}val or_vec = pure {lem: "or_vec", coq: "or_vec", ocaml: "or_vec", interpreter: "or_vec", lean: "_lean_bvor", _: "or_bits"}: forall ('n : Int).
(bits('n), bits('n)) -> bits('n)The 2048-bit logs bloom filter (YP §4.4.1), as 256 bytes.
type LogsBloom = vector(256, dec, byte)function k_emit_transfer_log¶
Emits the EIP-7708 transfer log for a nonzero, non-self value transfer (Amsterdam onward).
function k_emit_transfer_log(src : address, dst : address, v : word) -> unit = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let value_is_zero = word_is_zero(v);
if (profile.fork < Amsterdam) | value_is_zero | (src == dst) then {
return ()
};
let source = address_to_word(src);
let destination = address_to_word(dst);
let topics = LogTopics3((EIP7708_TRANSFER_TOPIC, source, destination));
let data = LogDataWord(v);
k_log(EIP7708_SYSTEM_ADDRESS, topics, data)
}Embeds a canonical-order address into the low 160 bits of an EVM word.
function address_to_word(bytes : address) -> word =
unsigned(
bytes[0]
@ bytes[1]
@ bytes[2]
@ bytes[3]
@ bytes[4]
@ bytes[5]
@ bytes[6]
@ bytes[7]
@ bytes[8]
@ bytes[9]
@ bytes[10]
@ bytes[11]
@ bytes[12]
@ bytes[13]
@ bytes[14]
@ bytes[15]
@ bytes[16]
@ bytes[17]
@ bytes[18]
@ bytes[19],
)Appends a log record (YP §4.4.1) to the transaction's log series.
function k_log(a : address, topics : LogTopics, data : LogData) -> unit = {
log_begin(a);
k_log_topics(topics);
k_log_data(data)
}function word_is_zero(w) = w == WORD_ZEROEIP-7954 code/initcode size bump (65536/131072).
let Amsterdam : int(amsterdam_fork_value) = sizeof(amsterdam_fork_value)let EIP7708_SYSTEM_ADDRESS : address = address_from_bits(
0x000000000000000000000000fffffffffffffffffffffffffffffffffffffffe,
)let EIP7708_TRANSFER_TOPIC : word = word_from_bits(0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef)The active protocol policy and all gas limits derived from the executing header, selected together while decoding the stateless input.
register k_execution_profile : ExecutionProfile = DEFAULT_EXECUTION_PROFILEA 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)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 k_emit_burn_log¶
Emits the EIP-7708 burn log when a selfdestruct deletion burns a nonzero balance (Amsterdam onward).
function k_emit_burn_log(a : address, v : word) -> unit = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let value_is_zero = word_is_zero(v);
if (profile.fork < Amsterdam) | value_is_zero then {
return ()
};
let address = address_to_word(a);
let topics = LogTopics2((EIP7708_BURN_TOPIC, address));
let data = LogDataWord(v);
k_log(EIP7708_SYSTEM_ADDRESS, topics, data)
}Embeds a canonical-order address into the low 160 bits of an EVM word.
function address_to_word(bytes : address) -> word =
unsigned(
bytes[0]
@ bytes[1]
@ bytes[2]
@ bytes[3]
@ bytes[4]
@ bytes[5]
@ bytes[6]
@ bytes[7]
@ bytes[8]
@ bytes[9]
@ bytes[10]
@ bytes[11]
@ bytes[12]
@ bytes[13]
@ bytes[14]
@ bytes[15]
@ bytes[16]
@ bytes[17]
@ bytes[18]
@ bytes[19],
)Appends a log record (YP §4.4.1) to the transaction's log series.
function k_log(a : address, topics : LogTopics, data : LogData) -> unit = {
log_begin(a);
k_log_topics(topics);
k_log_data(data)
}function word_is_zero(w) = w == WORD_ZEROEIP-7954 code/initcode size bump (65536/131072).
let Amsterdam : int(amsterdam_fork_value) = sizeof(amsterdam_fork_value)let EIP7708_BURN_TOPIC : word = word_from_bits(0xcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5)let EIP7708_SYSTEM_ADDRESS : address = address_from_bits(
0x000000000000000000000000fffffffffffffffffffffffffffffffffffffffe,
)The active protocol policy and all gas limits derived from the executing header, selected together while decoding the stateless input.
register k_execution_profile : ExecutionProfile = DEFAULT_EXECUTION_PROFILEA 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)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)