Skip to main content

Module state_update

Module state_update 

Source
Expand description

Targeted state-mutation vocabulary and the structured diff it produces (Pillar B.1 — the writer half of the event → state pipeline).

This module defines the small, generic vocabulary a future event decoder emits and EvmCache::apply_update consumes, plus the StateDiff that records what an apply actually changed. It is pure data and logic on itself: it carries no protocol or event knowledge and has no dependency on the cache implementation.

§The vocabulary

A StateUpdate is one targeted mutation:

§The dual-layer write-through policy

apply_update applies Slot writes through with one consistent rule: the BlockchainDb backend (layer 2) is written always; the CacheDB overlay (layer 1) is written only if an overlay account already exists for the address. A new overlay account is never materialized for a slot write — the read path falls through to the backend for an absent overlay entry, so a backend-only write is authoritative, and materializing an overlay entry would pollute layer 1 and could shadow later RPC reads. (This mirrors the established inject_storage_batch_fresh semantics.)

Account patches follow the same overlay-if-present write-through policy once the account is already present in either layer. If the account is absent from both layers, the patch is skipped and surfaced in StateDiff::skipped_accounts. Use StateUpdate::AccountUpsert when the caller intentionally wants to materialize a cold/default account.

§The output

Every apply returns a StateDiff of the changes it actually made: the SlotChanges, AccountChanges, and PurgeRecords. Only real changes are recorded — re-applying a value the cache already holds yields an empty diff, so idempotence is observable.

§Relative updates / cold-aware read-modify-write

Some callers learn only a delta (an ERC-20 Transfer log carries the transferred amount, not the resulting balances), so the vocabulary also supports relative updates: StateUpdate::SlotDelta reads the current slot value, applies a saturating SlotDelta (Add clamps at U256::MAX, Sub at U256::ZERO), and writes the result back through both layers. The general closure form is EvmCache::modify_slot.

A relative update is only valid against a value the cache actually holds. An un-fetched (“cold”) slot has no value, and applying a delta to it would compute 0 ± amount, write a wrong value, and (write-through) make it authoritative — silently corrupting state. So relative application is cold-aware: a SlotDelta on a cold slot is not applied; it is recorded in StateDiff::skipped as a SkippedDelta so the caller can fetch+seed the true value (the next read otherwise lazily fetches it). modify_slot hands its closure an Option<U256> (None when cold) and lets the caller decide.

§Masked writes to packed words

A storage slot often packs several fields into one word. A decoder that learns only some of those fields must update just its bits without clobbering the rest. StateUpdate::SlotMasked is the cold-aware masked read-modify-write for exactly this: it computes new = (old & !mask) | (value & mask), touching only the mask bits. Like SlotDelta it is cold-aware — the un-masked bits of a slot absent from both layers are unknown, so a masked write to a cold slot is not applied; it is surfaced in StateDiff::skipped_masks as a SkippedMask. (A full-mask SlotMasked matches an absolute Slot write on a hot slot but still skips on a cold one.)

The same relative, cold-aware rule extends to an account’s native balance: StateUpdate::BalanceDelta (and the closure form EvmCache::modify_account_balance) read-modify-write AccountInfo::balance, preserving nonce/code. “Cold” here means the account is absent from both layers (its balance is unknown); a BalanceDelta on a cold account is not applied — it is surfaced in StateDiff::skipped_balances as a SkippedBalanceDelta. This avoids materializing a default account that would mask the real on-chain one.

§Checking for skips

Because a cold-skipped relative update produces no change, it is invisible to the natural StateDiff::is_empty / StateDiff::len success check (those are changes-only). A caller applying relative updates must therefore check StateDiff::has_skipped (or inspect skipped, skipped_balances, skipped_masks, or skipped_accounts) — a cold target was dropped, not applied, and a silently-dropped balance/account update can break conservation. StateDiff::is_fully_applied and StateDiff::skipped_len are the companions.

§Boundary — events are Phase 4

This is the vocabulary a Phase 4 EventDecoder will emit into; Phase 3 does not decode events. Nothing here parses a Log or knows a protocol’s storage layout — that is the reader half of Pillar B and lands later.

Structs§

AccountChange
An account field delta. Each field is Some((old, new)) only when it changed.
AccountPatch
A partial account mutation: each Some field overwrites the cached value, each None leaves it unchanged. Setting code recomputes the code hash; Some(empty bytes) clears code to the empty-code hash.
PurgeRecord
Record of a purge: how much of each layer it removed.
SkippedAccountPatch
An account patch (StateUpdate::Account) that could not be applied because the account’s info is unknown to the EVM — absent from both cache layers, or present in the overlay as revm NotExisting.
SkippedBalanceDelta
A relative balance update (StateUpdate::BalanceDelta) that could not be applied because the account’s info is unknown to the EVM — absent from both cache layers, or present in the overlay as revm NotExisting (so its native balance is unknown).
SkippedDelta
A relative update (StateUpdate::SlotDelta) that could not be applied because the slot’s current value is unknown (not cached in either layer).
SkippedMask
A masked write (StateUpdate::SlotMasked) that could not be applied because the target slot’s current value is unknown (not cached in either layer).
StateDiff
What an apply_* call actually changed.

Enums§

PurgeScope
What part of an address’s cached state a purge removes.
SlotDelta
A relative storage-slot mutation: read the current value, transform it, and write it back.
StateUpdate
A single targeted mutation to cached EVM state.