pub struct EvmCache { /* private fields */ }Expand description
EVM cache with lazy-loading RPC backend.
Uses foundry-fork-db for intelligent caching and request deduplication.
Storage and account data is fetched on-demand when accessed during EVM execution,
eliminating the need for expensive access list prefetching.
Implementations§
Source§impl EvmCache
impl EvmCache
Sourcepub fn builder<P>(provider: Arc<P>) -> EvmCacheBuilder<P>where
P: Provider<AnyNetwork> + 'static,
pub fn builder<P>(provider: Arc<P>) -> EvmCacheBuilder<P>where
P: Provider<AnyNetwork> + 'static,
Start a fluent EvmCacheBuilder over the given provider.
Preferred over the positional with_cache /
new constructors for readability.
Sourcepub async fn new<P>(provider: Arc<P>) -> Selfwhere
P: Provider<AnyNetwork> + 'static,
pub async fn new<P>(provider: Arc<P>) -> Selfwhere
P: Provider<AnyNetwork> + 'static,
Create a new EvmCache with a SharedBackend that lazily fetches from RPC.
The backend spawns a background handler task that manages RPC requests and deduplicates concurrent requests for the same data.
§Runtime requirement
RPC-backed operation requires a multi-thread tokio runtime
(#[tokio::main(flavor = "multi_thread")] or
tokio::runtime::Builder::new_multi_thread()). The direct RPC callbacks
(eth_call and batch eth_getStorageAt) drive async work synchronously
via tokio::task::block_in_place, which is unsupported on a
current-thread runtime. On a current-thread runtime those callbacks
degrade to typed errors rather than panicking.
Sourcepub async fn at_block<P>(provider: Arc<P>, block: BlockId) -> Selfwhere
P: Provider<AnyNetwork> + 'static,
pub async fn at_block<P>(provider: Arc<P>, block: BlockId) -> Selfwhere
P: Provider<AnyNetwork> + 'static,
Create a new EvmCache pinned to an explicit block.
Prefer this over new when reproducibility matters and the
caller has already chosen the fork block.
Sourcepub async fn with_cache<P>(
provider: Arc<P>,
block: BlockId,
cache_config: Option<CacheConfig>,
spec_id: SpecId,
) -> Selfwhere
P: Provider<AnyNetwork> + 'static,
pub async fn with_cache<P>(
provider: Arc<P>,
block: BlockId,
cache_config: Option<CacheConfig>,
spec_id: SpecId,
) -> Selfwhere
P: Provider<AnyNetwork> + 'static,
Create a new EvmCache with disk-based caching.
This enables several caching features:
- Unified EVM state: Accounts + storage loaded from
evm_state.bin(bincode) - Bytecode caching: Contract bytecodes from
bytecodes.bin - Immutable data: Token decimals
§Runtime requirement
RPC-backed operation requires a multi-thread tokio runtime
(#[tokio::main(flavor = "multi_thread")] or
tokio::runtime::Builder::new_multi_thread()). The direct RPC callbacks
(eth_call and batch eth_getStorageAt) drive async work synchronously
via tokio::task::block_in_place, which is unsupported on a
current-thread runtime. On a current-thread runtime those callbacks
degrade to typed errors rather than panicking.
Sourcepub async fn with_cache_capacity<P>(
provider: Arc<P>,
block: BlockId,
cache_config: Option<CacheConfig>,
spec_id: SpecId,
shared_memory_capacity: SharedMemoryCapacity,
) -> Selfwhere
P: Provider<AnyNetwork> + 'static,
pub async fn with_cache_capacity<P>(
provider: Arc<P>,
block: BlockId,
cache_config: Option<CacheConfig>,
spec_id: SpecId,
shared_memory_capacity: SharedMemoryCapacity,
) -> Selfwhere
P: Provider<AnyNetwork> + 'static,
Like with_cache but takes an explicit
SharedMemoryCapacity controlling per-context EVM working-memory
pre-allocation. This is what EvmCacheBuilder::build calls; prefer the
builder. With SharedMemoryCapacity::Auto the buffer is sized from the
layer-2 storage loaded at construction (e.g. a bincode state file).
Sourcepub fn from_backend(
backend: SharedBackend,
blockchain_db: BlockchainDb,
block: BlockId,
chain_id: u64,
block_number: Option<u64>,
basefee: Option<u64>,
spec_id: SpecId,
) -> Self
pub fn from_backend( backend: SharedBackend, blockchain_db: BlockchainDb, block: BlockId, chain_id: u64, block_number: Option<u64>, basefee: Option<u64>, spec_id: SpecId, ) -> Self
Create a new EvmCache from an existing SharedBackend.
Useful when you want to share a backend between multiple caches (e.g. parallel simulation threads).
Shared pinned block. A SharedBackend owns a single pinned fork
height. Calling set_block / repin_to_block on any
cache built from the same backend re-pins the RPC fork height for all
of them. Sibling caches sharing one backend should agree on a block and not
re-pin independently; build separate backends if they must fork at
different heights.
Sourcepub fn flush(&self) -> Result<()>
pub fn flush(&self) -> Result<()>
Flush the cache state to disk.
This persists:
- Unified EVM state (accounts + storage) to
evm_state.bin(bincode) - Contract bytecodes to
bytecodes.bin - Immutable data (token decimals) to
immutable_data.bin
Call this after loading hot contract state and running simulations to speed up subsequent runs. The cache is also automatically flushed when the EvmCache is dropped.
Sourcepub fn cache_config(&self) -> Option<&CacheConfig>
pub fn cache_config(&self) -> Option<&CacheConfig>
Get the cache configuration, if any.
Returns None when the cache is purely in-memory (no disk persistence),
i.e. constructed without a CacheConfig or via
from_backend.
Sourcepub fn with_blockchain_db_mut<R>(
&mut self,
f: impl FnOnce(&BlockchainDb) -> R,
) -> R
pub fn with_blockchain_db_mut<R>( &mut self, f: impl FnOnce(&BlockchainDb) -> R, ) -> R
Run a synchronous direct mutation against the underlying BlockchainDb
and invalidate the memoized snapshot base afterwards.
This is the preferred escape hatch for unavoidable layer-2 map writes such
as accounts().write().insert(...) or storage().write().insert(...).
The closure still bypasses the CacheDB overlay and the normal write funnel,
so use higher-level mutators when they can express the change. Unlike
unchecked_blockchain_db, this wrapper
keeps the copy-on-write snapshot base honest automatically after in-place
overwrites whose map cardinality does not change.
Sourcepub fn unchecked_blockchain_db(&self) -> &BlockchainDb
pub fn unchecked_blockchain_db(&self) -> &BlockchainDb
Get an unchecked reference to the underlying BlockchainDb (the layer-2
backend store of accounts, storage, and bytecodes).
This exposes an internal store and bypasses the cache’s two-layer
consistency model: reads here see only the backend layer, not the CacheDB
overlay, and any writes performed through it skip the overlay. Prefer
higher-level accessors or with_blockchain_db_mut
for direct synchronous writes.
§Snapshot base
Writing layer 2 directly through this unchecked handle also bypasses the
memoized copy-on-write snapshot base (Pillar A). The next
create_snapshot only performs a count/absence
growth scan over layer 2, which catches lazy RPC-populated accounts/slots
because that path only appends at a fixed block. It does not catch
direct in-place changes where cardinality is unchanged: overwriting an
existing storage slot, or changing an existing account’s info/code/balance
without adding a new account, can leave a stale snapshot base. After such a
direct write, call
invalidate_snapshot_base (or re-pin via
set_block) before the next snapshot. Writes via the
crate’s own mutators (inject_storage_batch, apply_update, the inject_*
helpers, the purges) keep the base honest automatically.
Sourcepub fn unchecked_backend(&self) -> &SharedBackend
pub fn unchecked_backend(&self) -> &SharedBackend
Get an unchecked reference to the underlying SharedBackend (the lazy
RPC-backed fetcher shared across clones).
This exposes an internal handle and bypasses the cache’s two-layer consistency model: it reads/fetches directly without consulting the CacheDB overlay. Prefer the higher-level accessors; use with care.
§Snapshot base
Lazy RPC fetches through this backend only append missing accounts/slots at
the pinned block, so the snapshot growth scan catches them without an
explicit invalidation. Direct SharedBackend::insert_or_update_storage /
insert_or_update_address calls are different: they enqueue a background
handler request that can rewrite layer-2 entries in place, leaving the
memoized copy-on-write base stale at an unchanged slot/account count.
If you use those helpers directly, first synchronize with the backend
handler by reading back the updated account/slot through SharedBackend
(for example via basic_ref / storage_ref), then call
invalidate_snapshot_base before the next
create_snapshot. Calling
invalidate_snapshot_base immediately after insert_or_update_* is not, by
itself, a guarantee that the queued update has been applied before the next
snapshot.
Sourcepub fn db_mut(&mut self) -> &mut ForkCacheDB
pub fn db_mut(&mut self) -> &mut ForkCacheDB
Get a mutable reference to the underlying ForkCacheDB (the layer-1
CacheDB overlay).
This exposes an internal and bypasses the cache’s two-layer consistency model: writes made here land only in the overlay and are not mirrored into the BlockchainDb backend, so parallel tasks sharing the backend will not see them. Prefer the higher-level mutators; use with care.
Sourcepub fn rpc_call(&self, to: Address, calldata: Bytes) -> Option<Result<Bytes>>
pub fn rpc_call(&self, to: Address, calldata: Bytes) -> Option<Result<Bytes>>
Make a direct RPC eth_call to the node, bypassing revm simulation.
This is much faster than call_raw for batch operations because the RPC
node has all state in memory and doesn’t need lazy storage fetching.
Returns None if no RPC caller is available (e.g. from_backend constructor).
§Panics
Must be called from within a multi-thread tokio runtime: the callback
drives the async eth_call to completion via
tokio::task::block_in_place. On a current-thread runtime (or with no
runtime), the callback degrades to an Err rather than panicking, but
block_in_place itself will panic if invoked from a non-worker thread of
a multi-thread runtime.
Sourcepub fn storage_batch_fetcher(&self) -> Option<&StorageBatchFetchFn>
pub fn storage_batch_fetcher(&self) -> Option<&StorageBatchFetchFn>
Get the batch storage fetcher, if available.
Returns None when constructed via from_backend (no provider available).
§Panics
The returned StorageBatchFetchFn must be invoked from within a
multi-thread tokio runtime: it drives concurrent eth_getStorageAt
calls to completion via tokio::task::block_in_place. On a
current-thread runtime (or with no runtime) it degrades to an Err
result for every requested slot rather than panicking, but
block_in_place itself will panic if invoked from a non-worker thread of
a multi-thread runtime.
Sourcepub fn inject_storage_batch(&mut self, results: &[(Address, U256, U256)])
pub fn inject_storage_batch(&mut self, results: &[(Address, U256, U256)])
Inject batch-fetched storage values directly into BlockchainDb (layer 2).
This bypasses SharedBackend and makes values available for subsequent
storage_ref() calls and EVM SLOADs. Used after StorageBatchFetchFn
returns results to populate the cache in bulk.
Takes &mut self (as of Pillar A) so it can mark each touched address dirty
for the memoized copy-on-write base; the write itself is still a direct
layer-2 backend write. Overwriting an existing slot at an unchanged slot
count is invalidated here too, since the refresh_base growth scan only
catches length changes.
Sourcepub fn inject_storage_batch_fresh(&mut self, results: &[(Address, U256, U256)])
pub fn inject_storage_batch_fresh(&mut self, results: &[(Address, U256, U256)])
Inject freshly-fetched storage values, healing both cache layers.
Like inject_storage_batch this writes each
value into the BlockchainDb backend (layer 2). Additionally, for any
address that already has a CacheDB overlay entry (layer 1), it writes
the slot into that overlay too.
This matters because both create_snapshot and
the synchronous EVM SLOAD path let the overlay win over the backend. A
correction written only to layer 2 would be shadowed by a stale layer-1
slot, so the cache could never converge — the freshness validator would
re-detect the same change and re-correct it every cycle. Writing through
the overlay keeps the layer that wins authoritative.
It deliberately does not create a new overlay account for an address that has none: such a slot is layer-2-only (e.g. cold prefetch), where the backend write is already authoritative and materializing an overlay entry would pollute layer 1 and could shadow later RPC reads.
Sourcepub fn apply_update(&mut self, update: &StateUpdate) -> StateDiff
pub fn apply_update(&mut self, update: &StateUpdate) -> StateDiff
Apply a single targeted StateUpdate, returning a StateDiff of what
actually changed.
This is the single primitive that writes the state-update vocabulary
across both cache layers with one consistent, documented policy. It is
synchronous and infallible — a write, not a fetch, so it never touches
RPC and never errors. See the state_update module
for the dual-layer write-through policy and the diff semantics.
StateUpdate::Slot— writevalueinto the backend (layer 2) always, and into the overlay (layer 1) only if an overlay account already exists. Records aSlotChangeonly when the value actually changes (old.unwrap_or(ZERO) != value).StateUpdate::SlotDelta— relative, cold-aware. If the slot has a cached value, write the saturating delta through the same path and record aSlotChangeiff it changed; if the slot is cold (absent from both layers), apply nothing and surface aSkippedDeltaindiff.skipped.StateUpdate::BalanceDelta— relative, cold-aware native-balance update. If the account is present in either layer, apply the saturating delta to its balance (nonce/code preserved) write-through and record anAccountChangeiff it changed; if the account is cold (absent from both layers), apply nothing and surface aSkippedBalanceDeltaindiff.skipped_balances(no default account is materialized).StateUpdate::Account— load the currentAccountInfofrom the cached layers (no RPC), apply eachSomepatch field (recomputing the code hash whencodeis set), then write through with the same layer policy. Records anAccountChangewithSome((old, new))only for fields that changed. If the account is cold (absent from both layers), apply nothing and surface aSkippedAccountPatchindiff.skipped_accounts.StateUpdate::AccountUpsert— same patch semantics, but intentionally materializes a cold/default account when absent from both layers.StateUpdate::Purge— dispatch to the matching purge layer logic and record aPurgeRecord.
§Warning — relative updates can be skipped
A cold-aware update targeting a cold address is dropped, not applied
unless it is an explicit StateUpdate::AccountUpsert. Because a skip
produces no change, it is invisible to the changes-only
StateDiff::is_empty / StateDiff::len success check, so after
applying cold-aware updates the caller must inspect
StateDiff::has_skipped (or the skipped_* fields) and fetch+seed the
cold target.
let contract = Address::repeat_byte(0x01);
let diff = cache.apply_update(&StateUpdate::slot(contract, U256::from(0), U256::from(42)));
assert_eq!(diff.slots.len(), 1);Sourcepub fn apply_updates(&mut self, updates: &[StateUpdate]) -> StateDiff
pub fn apply_updates(&mut self, updates: &[StateUpdate]) -> StateDiff
Apply a batch of StateUpdates left-to-right, merging each per-update
StateDiff.
Later updates observe the effect of earlier ones: two Slot writes to the
same key record old → a then a → b. Like
apply_update this is synchronous and infallible.
§Performance — batched single-lock fast-path
Consecutive Slot/SlotDelta writes are processed holding the backend
storage write-guard once for the run (the overlay map is lock-free), so
a bulk slot seed pays one lock acquisition instead of one read + one write
lock per slot. Apply order is preserved: when an Account/BalanceDelta/
Purge update is reached the guard is dropped first (those take the
accounts() / storage() locks themselves — holding the storage
write-guard across them would deadlock the non-reentrant RwLock), the
update is processed via apply_update, then the guard
is lazily re-acquired on the next slot run. The result is byte-identical to
folding apply_update over the batch.
§Warning — relative updates can be skipped
See apply_update: a cold relative update is dropped,
not applied, and is invisible to StateDiff::is_empty /
StateDiff::len. After a batch with relative updates, check
StateDiff::has_skipped.
Sourcepub fn modify_slot(
&mut self,
address: Address,
slot: U256,
f: impl FnOnce(Option<U256>) -> Option<U256>,
) -> Option<SlotChange>
pub fn modify_slot( &mut self, address: Address, slot: U256, f: impl FnOnce(Option<U256>) -> Option<U256>, ) -> Option<SlotChange>
Read-modify-write one storage slot through a caller-supplied transform.
The general closure escape hatch behind StateUpdate::SlotDelta (the
data-level form flows through apply_update; this is
for arbitrary transforms). f is called with the current cached value
(overlay ▸ backend ▸ None when the slot is cold) and decides the new
value:
Some(new)writesnewthrough both layers (the same write path asStateUpdate::Slot) and returns aSlotChangeiff it changed (old.unwrap_or(ZERO) != new);Nonewrites nothing and returnsNone.
The caller owns the cold/overflow policy. To skip cold slots (the
cold-aware read-modify-write rule), map through the Option:
|cur| cur.map(|v| v.saturating_add(amount)) leaves a cold slot untouched.
To write an absolute value regardless, ignore the argument: |_| Some(v).
let token = Address::repeat_byte(0x01);
let slot = U256::from(0);
// Saturating +100, but only if the slot is already hot.
let change = cache.modify_slot(token, slot, |cur| cur.map(|v| v.saturating_add(U256::from(100))));Sourcepub fn modify_account_balance(
&mut self,
address: Address,
f: impl FnOnce(Option<U256>) -> Option<U256>,
) -> Option<AccountChange>
pub fn modify_account_balance( &mut self, address: Address, f: impl FnOnce(Option<U256>) -> Option<U256>, ) -> Option<AccountChange>
Read-modify-write an account’s native balance through a caller-supplied transform.
The closure analog of StateUpdate::BalanceDelta (the data-level form
flows through apply_update; this is for arbitrary
transforms). f is called with the account’s current native balance
(overlay ▸ backend ▸ None when the account is absent from both
layers) and decides the new balance:
Some(new)writesnewthrough both layers — backend always, overlay only if an overlay account already exists — preserving the account’s nonce and code, and returns anAccountChange(balance only) iff the balance changed;Nonewrites nothing (no account is materialized) and returnsNone.
“Cold” for a balance is the account being absent from both layers — or
present in the overlay as revm NotExisting (absent to the EVM), which the
internal account read also treats as cold, mirroring DbAccount::info().
To skip cold accounts, map through the Option:
|cur| cur.map(|v| v.saturating_add(amount)).
let acct = Address::repeat_byte(0x01);
// Saturating +100, but only if the account's balance is already known.
let change = cache.modify_account_balance(acct, |cur| cur.map(|v| v.saturating_add(U256::from(100))));Sourcepub fn set_storage_batch_fetcher(&mut self, f: StorageBatchFetchFn)
pub fn set_storage_batch_fetcher(&mut self, f: StorageBatchFetchFn)
Set (or replace) the batch storage fetcher.
This is the seam the freshness controller and tests use to drive
re-verification without a live provider: a stubbed
StorageBatchFetchFn can be injected over a mocked-provider cache.
Sourcepub fn cached_storage_value(&self, address: Address, slot: U256) -> Option<U256>
pub fn cached_storage_value(&self, address: Address, slot: U256) -> Option<U256>
Return the currently-cached value for a storage slot, if any.
Mirrors what the EVM would SLOAD from the cached layers (it never touches
RPC, unlike read_storage_slot):
- The CacheDB overlay (layer 1) wins: if the overlay account holds the slot, return it.
- Match revm’s
CacheDB::storage_ref: if the overlay account exists but does not hold the slot, and itsaccount_stateisStorageClearedorNotExisting, the live EVM reads the slot as ZERO and never consults the backend — so returnSome(U256::ZERO), not the (shadowed) backend value. Returning the backend value here would let aSlotDelta/modify_slotcompute a delta against a base the EVM never sees (silent corruption) and would mis-recordapply_slot’sold. - Otherwise fall through to the BlockchainDb backend (layer 2);
Nonewhen neither layer has seen the slot.
Sourcepub fn verify_slots(
&mut self,
slots: &[(Address, U256)],
) -> Result<Vec<SlotChange>>
pub fn verify_slots( &mut self, slots: &[(Address, U256)], ) -> Result<Vec<SlotChange>>
Re-fetch the given slots via the batch fetcher, compare to the currently cached values, and inject the ones that changed.
For each slot whose freshly-fetched value differs from the cached value,
the fresh value is written into the cache via
inject_storage_batch_fresh and a
SlotChange is recorded. Slots that are unchanged, or that the fetcher
fails to return, are left as-is. Returns the set of changed slots.
Requires a batch fetcher (set at construction or via
set_storage_batch_fetcher); errors if
none is available. This is the synchronous main-thread primitive; the
background validator performs the equivalent comparison against a snapshot.
Sourcepub fn reconcile_slots(
&mut self,
slots: &[(Address, U256)],
) -> Result<Vec<SlotChange>>
pub fn reconcile_slots( &mut self, slots: &[(Address, U256)], ) -> Result<Vec<SlotChange>>
Reconciliation re-read used by EventPipeline::reconcile.
Like verify_slots it fetches the requested slots,
injects the ones that changed, and returns the changed set — but it is
honest about reachability: it errors not only when no batch fetcher is
configured, but also when a non-empty request could not fetch any slot
(a total fetch failure — e.g. the default RPC fetcher invoked with no usable
runtime, or an unreachable provider). Reconciliation that silently “verified
nothing” would be a false all-clear, so it surfaces as an error for the
caller to retry. A partially-successful fetch returns Ok with whatever
changed.
Sourcepub fn purge_account(&mut self, addr: Address)
pub fn purge_account(&mut self, addr: Address)
Purge an account fully from both cache layers: its AccountInfo
(balance/nonce/code hash) and all of its storage.
Removes addr from the CacheDB overlay accounts map, the BlockchainDb
accounts map, and the BlockchainDb storage map, so the next access
re-fetches a clean account from RPC. This is the account-level
counterpart to the storage-only purge_contract_storage:
use it when an address is fully volatile (no pinned slots) and even its
balance/nonce/code can no longer be trusted.
Sourcepub fn set_chain_id(&mut self, chain_id: u64)
pub fn set_chain_id(&mut self, chain_id: u64)
Set the chain ID reported to simulations via the CHAINID opcode.
Prefer setting this at construction through
EvmCacheBuilder::chain_id. This setter exists for cases where the
chain ID must change after construction. It takes effect on the next
create_snapshot / build_evm; existing
snapshots and overlays keep the chain ID captured when they were created.
Sourcepub fn snapshot(&self) -> Cache
pub fn snapshot(&self) -> Cache
Take a low-level, same-thread snapshot of the CacheDB overlay for in-place restore.
Clones the inner revm::database::Cache (the layer-1 overlay’s
accounts and storage) only — not the underlying database wrapper or the
BlockchainDb backend. Pair with restore to roll the
overlay back on the same EvmCache after speculative mutations (this is
how the balance-slot scan probes and rewinds).
For cross-thread fan-out use create_snapshot
instead: it merges both layers into an Arc<EvmSnapshot> that is
Send + Sync and can be shared with parallel simulators via
EvmOverlay.
Sourcepub fn restore(&mut self, snapshot: Cache)
pub fn restore(&mut self, snapshot: Cache)
Restore the CacheDB overlay from a snapshot taken with
snapshot.
Overwrites the layer-1 overlay wholesale with snapshot, discarding any
overlay mutations made since it was taken. The BlockchainDb backend is
untouched. This is the in-place counterpart to the cross-thread
create_snapshot / EvmOverlay path.
Sourcepub fn session(&mut self) -> EvmSession<'_>
pub fn session(&mut self) -> EvmSession<'_>
Create a new session for executing multiple operations.
Changes made within the session are only committed to the underlying database
when session.commit() is called. Dropping the session without calling commit
discards all changes made during the session.
Sourcepub fn create_snapshot(&mut self) -> Arc<EvmSnapshot> ⓘ
pub fn create_snapshot(&mut self) -> Arc<EvmSnapshot> ⓘ
Create an immutable, Send + Sync snapshot of the current EVM state for
cross-thread fan-out (the copy-on-write two-tier view, Pillar A).
Rather than deep-copying both layers, this memoizes the cold layer-2
(BlockchainDb) index as an Arc-shared base — reused as a cheap
Arc::clone when layer 2 is unchanged, rebuilt copy-on-write only for the
addresses that changed — and folds the hot layer-1 (CacheDB overlay)
delta over it. Layer-1 values shadow the base on reads, reproducing the
live cache’s layered semantics; the resulting EvmSnapshot is shared
across threads via Arc. Its cost tracks changed state, not total
state. (The retained create_snapshot_deep_clone
is the read-equivalent O(total) reference, kept for benchmarking/testing.)
Takes &mut self because it refreshes and memoizes the base. For cheap
same-thread save/restore of just the overlay, prefer
snapshot / restore instead.
Sourcepub fn invalidate_snapshot_base(&mut self)
pub fn invalidate_snapshot_base(&mut self)
Force the next create_snapshot to rebuild the
memoized copy-on-write base from scratch (Pillar A).
The crate’s own mutators keep the base honest automatically. This is the
escape-hatch re-honest hook: call it after writing layer 2 directly
through unchecked_blockchain_db or
unchecked_backend — those bypass the write
funnel, and in-place changes at unchanged cardinality are invisible to the
snapshot growth scan.
That includes overwriting an existing storage slot and changing an existing
account’s info/code/balance without adding a new account. Lazy RPC-populated
data does not need this call because it only appends accounts/slots, which
the growth scan catches.
When using SharedBackend::insert_or_update_* through
unchecked_backend, remember those helpers only
enqueue a background update. Synchronize/read back the update through
SharedBackend before the next snapshot; invalidate_snapshot_base alone
is not a backend-handler synchronization point. Once the direct write is
present, calling this before the next snapshot guarantees it reflects that
write rather than a stale memoized value. Over-invalidation is always safe
(Decision D2); the only cost is one full base rebuild on the next snapshot.
Sourcepub fn set_block(&mut self, block: BlockId)
pub fn set_block(&mut self, block: BlockId)
Update the block that RPC fetches are pinned to.
This re-pins the SharedBackend and the batch storage fetcher to block,
so subsequent RPC fetches read state at the new block.
§Block-context contract
To prevent the EVM block context from silently diverging from the pinned
block, when block is a concrete BlockId::Number(Number(n)) this also
updates block_number (the NUMBER opcode) to n. For tag-based block
ids (latest, pending, hashes, etc.), the height is not
statically known, so block_number is cleared.
basefee (the BASEFEE opcode) is cleared on every block change and
on every non-concrete tag/hash pin call because deriving it requires
fetching the block header, which this synchronous method cannot do. Callers
that change blocks should refresh it via
set_block_context after fetching the new
header. Prefer repin_to_block when re-pinning to
a concrete height, since it keeps block_number and the pinned block in
lockstep.
Sourcepub fn set_timestamp(&mut self, timestamp: Option<u64>)
pub fn set_timestamp(&mut self, timestamp: Option<u64>)
Set a custom timestamp for EVM simulations.
When set, all EVM executions will use this timestamp instead of the current system time. This is useful for simulating future blocks to predict when time-dependent opportunities (like yield farming rewards) become profitable.
Pass None to use the current system time (default behavior).
Sourcepub fn timestamp(&self) -> Option<u64>
pub fn timestamp(&self) -> Option<u64>
Get the current timestamp override, if any.
Returns None if the cache is using the current system time (default).
Sourcepub fn block_number(&self) -> Option<u64>
pub fn block_number(&self) -> Option<u64>
Get the block number used for EVM simulations (the NUMBER opcode).
Fetched from the pinned block’s header at construction. Concrete-number
pins set it via set_block /
repin_to_block; tag/hash pins clear it
because their height is not statically known. None means revm falls back
to 0, which can steer contracts that branch on block.number down a
different code path. Override directly via
set_block_context.
Sourcepub fn basefee(&self) -> Option<u64>
pub fn basefee(&self) -> Option<u64>
Get the base fee per gas used for EVM simulations (the BASEFEE opcode).
Fetched from the pinned block’s header at construction. None means
revm falls back to 0. This is cleared by set_block
/ repin_to_block when the pin changes, and by
non-concrete tag/hash pin calls because those can drift without a
concrete number in the API. Refresh it with
set_block_context after fetching a new header
if BASEFEE accuracy matters.
Sourcepub fn set_block_context(
&mut self,
block_number: Option<u64>,
basefee: Option<u64>,
)
pub fn set_block_context( &mut self, block_number: Option<u64>, basefee: Option<u64>, )
Update the block context for EVM simulations.
Call this when the simulation block changes (e.g. at the start of each search cycle) to keep NUMBER and BASEFEE opcodes accurate.
Sourcepub fn set_basefee(&mut self, basefee: U256)
pub fn set_basefee(&mut self, basefee: U256)
Set the block base fee (the BASEFEE opcode) for subsequent simulations,
propagated into the next create_snapshot.
Offline caches built over a mocked provider have no fetched block header,
so the base fee is unset (and the BASEFEE opcode reads 0). Use this to
install one explicitly — it determines the priority fee
(gas_price − basefee) credited to the beneficiary, and thus the
coinbase_payment a simulate_bundle reports.
The cache stores the base fee as a u64 (matching the block header and the
EvmSnapshot field), so a U256 larger than u64::MAX is saturated.
Sourcepub fn set_coinbase(&mut self, coinbase: Option<Address>)
pub fn set_coinbase(&mut self, coinbase: Option<Address>)
Override the block beneficiary (the COINBASE opcode) for subsequent
simulations.
Set this when simulating logic that reads block.coinbase (e.g.
MEV/builder tip accounting). None lets revm use its default beneficiary.
Sourcepub fn set_prevrandao(&mut self, prevrandao: Option<B256>)
pub fn set_prevrandao(&mut self, prevrandao: Option<B256>)
Override prevrandao (the PREVRANDAO opcode, the post-merge header mix
hash) for subsequent simulations.
Set this when reproducing contracts that source on-chain randomness from
block.prevrandao. None leaves revm’s default in place.
Sourcepub fn set_block_gas_limit(&mut self, gas_limit: Option<u64>)
pub fn set_block_gas_limit(&mut self, gas_limit: Option<u64>)
Override the block gas limit (the GASLIMIT opcode) for subsequent
simulations.
Set this when simulating logic that reads block.gaslimit. None lets
revm use its default.
Sourcepub fn repin_to_block(&mut self, block_number: u64)
pub fn repin_to_block(&mut self, block_number: u64)
Re-pin the cache to a specific block number.
Updates the SharedBackend pinned block, the batch fetcher block, and the
EVM block context (NUMBER opcode) in lockstep. The current basefee is
cleared because it cannot be refreshed synchronously; callers should set it
via set_block_context after fetching the new
block header if BASEFEE accuracy matters.
Sourcepub async fn ensure_account(&mut self, address: Address) -> Result<()>
pub async fn ensure_account(&mut self, address: Address) -> Result<()>
Ensure an account is loaded into the cache.
With the lazy-loading backend, this is optional - accounts are fetched automatically when accessed. However, you can use this to pre-warm the cache for specific accounts.
Sourcepub fn read_storage_slot(
&mut self,
address: Address,
slot: U256,
) -> Result<U256>
pub fn read_storage_slot( &mut self, address: Address, slot: U256, ) -> Result<U256>
Read a single storage slot through the SharedBackend (BlockchainDb -> RPC fallback).
After purge_contract_slots removes a slot from BlockchainDb, this method fetches
fresh data from RPC and caches it in BlockchainDb. Subsequent EVM SLOADs find
the value there without additional RPC calls.
Sourcepub fn insert_storage_slot(
&mut self,
address: Address,
slot: U256,
value: U256,
) -> Result<()>
pub fn insert_storage_slot( &mut self, address: Address, slot: U256, value: U256, ) -> Result<()>
Write a raw storage slot value directly into the CacheDB layer.
Subsequent EVM SLOADs for this (address, slot) will read the injected value without any RPC call. Used for hot-state injection where we already know the current on-chain value from WebSocket events.
Sourcepub fn seed_erc20_balance_slots(
&mut self,
slots: impl IntoIterator<Item = (Address, U256)>,
)
pub fn seed_erc20_balance_slots( &mut self, slots: impl IntoIterator<Item = (Address, U256)>, )
Pre-seed known ERC20 balanceOf mapping base slots, keyed by token.
Each (token, slot) records the storage slot of the token’s
mapping(address => uint256) balances, letting
set_erc20_balance_with_slot_scan
skip its 0..=max_slot probing pass for that token and write the balance
directly. Seeding a wrong slot is self-correcting: the scan verifies the
write and falls back to a fresh probe (evicting the bad seed) if it
fails. Later entries overwrite earlier ones for the same token.
Sourcepub fn insert_mapping_storage_slot(
&mut self,
contract: Address,
slot: U256,
slot_address: Address,
value: U256,
) -> Result<()>
pub fn insert_mapping_storage_slot( &mut self, contract: Address, slot: U256, slot_address: Address, value: U256, ) -> Result<()>
Write a value into a Solidity mapping(address => ...) entry on
contract, at the mapping declared at base slot slot.
Computes the entry’s storage key as
keccak256(abi.encode(slot_address, slot)) — Solidity’s layout for an
address-keyed mapping — and writes value there in the CacheDB overlay.
Used to forge ERC20 balances and allowances without an on-chain transfer.
§Errors
Returns an error if the underlying CacheDB storage insert fails (e.g. the account cannot be loaded from the backend).
Sourcepub fn set_erc20_balance_with_slot_scan(
&mut self,
token: Address,
owner: Address,
amount: U256,
max_slot: u16,
) -> Result<bool>
pub fn set_erc20_balance_with_slot_scan( &mut self, token: Address, owner: Address, amount: U256, max_slot: u16, ) -> Result<bool>
Set an ERC20 balance by probing storage mapping slots until balanceOf(owner) reflects
a probe value, then writing amount to the discovered slot.
Returns Ok(true) if the balance was set and verified, Ok(false) if no slot in
0..=max_slot matched, and Err on EVM/cache failures.
Sourcepub fn call(
&mut self,
from: Address,
to: Address,
calldata: Bytes,
commit: bool,
) -> Result<ExecutionResult>
pub fn call( &mut self, from: Address, to: Address, calldata: Bytes, commit: bool, ) -> Result<ExecutionResult>
Execute a call with automatic account/storage fetching.
Unlike the old implementation, this does NOT prefetch via access lists. The SharedBackend lazily fetches any missing data during execution.
Sourcepub fn call_raw(
&mut self,
from: Address,
to: Address,
calldata: Bytes,
commit: bool,
) -> Result<ExecutionResult>
pub fn call_raw( &mut self, from: Address, to: Address, calldata: Bytes, commit: bool, ) -> Result<ExecutionResult>
Execute a call without any prefetching.
Data is fetched lazily by the SharedBackend as needed during execution.
Sourcepub fn call_sol<C>(&mut self, to: Address, call: C) -> Result<C::Return>where
C: SolCall,
pub fn call_sol<C>(&mut self, to: Address, call: C) -> Result<C::Return>where
C: SolCall,
Execute a non-committing typed Solidity call from Address::ZERO.
This is the typed equivalent of encoding a SolCall, passing it to
call_raw with commit = false, and decoding the
successful return data with SolCall::abi_decode_returns.
sol! {
function balanceOf(address account) external view returns (uint256);
}
let balance = cache.call_sol(token, balanceOfCall { account: owner })?;Sourcepub fn call_sol_from<C>(
&mut self,
from: Address,
to: Address,
call: C,
) -> Result<C::Return>where
C: SolCall,
pub fn call_sol_from<C>(
&mut self,
from: Address,
to: Address,
call: C,
) -> Result<C::Return>where
C: SolCall,
Sourcepub fn call_sol_with<C>(
&mut self,
from: Address,
to: Address,
call: C,
tx: &TxConfig,
) -> Result<C::Return>where
C: SolCall,
pub fn call_sol_with<C>(
&mut self,
from: Address,
to: Address,
call: C,
tx: &TxConfig,
) -> Result<C::Return>where
C: SolCall,
Execute a non-committing typed Solidity call with explicit tx overrides.
This is the typed equivalent of call_raw_with
with commit = false.
Sourcepub fn transact_sol<C>(
&mut self,
from: Address,
to: Address,
call: C,
tx: &TxConfig,
) -> Result<C::Return>where
C: SolCall,
pub fn transact_sol<C>(
&mut self,
from: Address,
to: Address,
call: C,
tx: &TxConfig,
) -> Result<C::Return>where
C: SolCall,
Execute a typed Solidity call and commit its state changes.
This is the typed equivalent of call_raw_with
with commit = true; the call’s state changes are persisted through the
same path as the raw committing API before the return data is decoded.
Sourcepub fn call_raw_with(
&mut self,
from: Address,
to: Address,
calldata: Bytes,
commit: bool,
tx: &TxConfig,
) -> Result<ExecutionResult>
pub fn call_raw_with( &mut self, from: Address, to: Address, calldata: Bytes, commit: bool, tx: &TxConfig, ) -> Result<ExecutionResult>
Sourcepub fn call_raw_with_access_list(
&mut self,
from: Address,
to: Address,
calldata: Bytes,
) -> Result<(ExecutionResult, StorageAccessList)>
pub fn call_raw_with_access_list( &mut self, from: Address, to: Address, calldata: Bytes, ) -> Result<(ExecutionResult, StorageAccessList)>
Execute a non-committing call and extract the access list of touched accounts and storage slots before reverting.
Used for EIP-2929 marginal gas estimation in batched simulations.
Sourcepub fn call_logs(
&mut self,
from: Address,
to: Address,
calldata: Bytes,
commit: bool,
) -> Result<(Vec<Log>, u64)>
pub fn call_logs( &mut self, from: Address, to: Address, calldata: Bytes, commit: bool, ) -> Result<(Vec<Log>, u64)>
Execute a call and return its emitted logs and gas used.
A thin wrapper over call that requires success and
discards the return data. When commit is true the call’s state changes
are persisted to the CacheDB overlay; otherwise they are reverted.
§Errors
Returns an error if the underlying transact fails, or if the call did not
Success (i.e. it reverted or halted).
Sourcepub fn erc20_balance_of(
&mut self,
token: Address,
owner: Address,
) -> Result<U256>
pub fn erc20_balance_of( &mut self, token: Address, owner: Address, ) -> Result<U256>
Read an ERC20 token balance by simulating a balanceOf(owner) call.
Non-committing: the read is reverted, so it never mutates cache state.
§Errors
Returns an error if the simulated call fails or does not Success (e.g.
token is not a contract or reverts), or if the returned data cannot be
ABI-decoded as a uint256.
Sourcepub fn erc20_allowance(
&mut self,
token: Address,
owner: Address,
spender: Address,
) -> Result<U256>
pub fn erc20_allowance( &mut self, token: Address, owner: Address, spender: Address, ) -> Result<U256>
Read an ERC20 allowance by simulating an allowance(owner, spender) call.
Non-committing: the read is reverted, so it never mutates cache state.
§Errors
Returns an error if the simulated call fails or does not Success (e.g.
token is not a contract or reverts), or if the returned data cannot be
ABI-decoded as a uint256.
Sourcepub fn erc20_decimals(&mut self, token: Address) -> Result<u8>
pub fn erc20_decimals(&mut self, token: Address) -> Result<u8>
Read an ERC20 token’s decimals by simulating a decimals() call.
Memoized: a hit in the in-memory token-decimals map returns immediately
without simulating. On a miss the value is resolved by a non-committing
decimals() call.
§Side effects
On a miss the resolved value is cached in both the in-memory
token-decimals map (process lifetime) and the immutable data cache
(so it is persisted to disk on the next flush).
§Errors
Returns an error if the simulated call fails or does not Success (e.g.
token is not a contract or reverts), or if the returned data cannot be
ABI-decoded as a uint8.
Sourcepub fn immutable_cache(&self) -> &ImmutableDataCache
pub fn immutable_cache(&self) -> &ImmutableDataCache
Get a reference to the immutable data cache (token decimals).
Sourcepub fn immutable_cache_mut(&mut self) -> &mut ImmutableDataCache
pub fn immutable_cache_mut(&mut self) -> &mut ImmutableDataCache
Get a mutable reference to the immutable data cache.
Use this to pre-populate token decimals that would otherwise be discovered
lazily. Entries are persisted on the next flush (and on
drop) when a CacheConfig is set.
Sourcepub fn has_contract_storage(&self, address: Address) -> bool
pub fn has_contract_storage(&self, address: Address) -> bool
Check if an address has storage slots pre-loaded in the BlockchainDb.
This is useful to determine if we loaded the EVM state from the unified
evm_state.bin cache and an address already has reusable storage.
§Arguments
address- The contract address to check
§Returns
true if the address has any storage slots in the underlying BlockchainDb,
false otherwise
Sourcepub fn contract_storage_slot_count(&self, address: Address) -> usize
pub fn contract_storage_slot_count(&self, address: Address) -> usize
Get the number of storage slots loaded for a contract address.
Useful for debugging and logging to understand cache state.
Get memory statistics for the shared memory buffer used during EVM simulations.
Returns a tuple of (current_capacity_bytes, current_length_bytes).
The capacity represents the high-water mark of memory usage across all simulations since the buffer grows but doesn’t shrink. The length is typically 0 between simulations (cleared after each use).
§Use Case
Call this after running a batch of simulations to understand memory usage
and inform the optimal initial capacity for SharedMemory.
§Example
let (capacity, _len) = cache.shared_memory_stats();
println!("Peak memory usage: {} KB", capacity / 1024);Log the current shared memory buffer statistics.
Useful for profiling after running a batch of simulations.
Pre-allocate the shared memory buffer to a specific capacity.
Use this after measuring peak usage to avoid reallocation overhead during simulations. The buffer will grow beyond this if needed, but pre-sizing to the expected peak eliminates allocations.
§Arguments
capacity- The capacity in bytes to reserve
§Example
// After profiling shows peak usage is ~32KB
cache.reserve_shared_memory(32 * 1024);The resolved per-context EVM shared-memory pre-allocation, in bytes.
This is the SharedMemoryCapacity configured on the
EvmCacheBuilder resolved to a concrete size (with
SharedMemoryCapacity::Auto resolved against the state loaded at
construction), raised by any later reserve_shared_memory.
Each create_snapshot copies it onto the snapshot
so snapshot-backed EvmOverlays pre-allocate the same amount.
Sourcepub fn purge_contract_storage(&mut self, address: Address) -> usize
pub fn purge_contract_storage(&mut self, address: Address) -> usize
Purge all storage slots for a specific contract from both cache layers.
This clears:
- CacheDB overlay (
self.db.cache.accounts[addr].storage) - the in-memory layer that caches storage slots fetched during EVM execution. Without clearing this layer, subsequent EVM calls return stale values even after the backend is purged. - BlockchainDb backend (
self.blockchain_db.storage()) - the persistent layer that caches RPC responses and is loaded fromevm_state.bin.
After purging both layers, the next EVM read for this contract’s storage will go all the way to the RPC for fresh data.
Sourcepub fn purge_contract_slots(
&mut self,
address: Address,
slots: &[U256],
) -> usize
pub fn purge_contract_slots( &mut self, address: Address, slots: &[U256], ) -> usize
Purge specific storage slots for a contract from both cache layers.
Unlike purge_contract_storage() which removes ALL storage, this only removes
the specified slots. This is useful when only a narrow subset of hot storage
became stale and the rest of the contract’s cached storage should be kept.
Returns the number of slots removed from the BlockchainDb backend.
Sourcepub fn purge_contracts_storage(
&mut self,
addresses: impl IntoIterator<Item = Address>,
) -> usize
pub fn purge_contracts_storage( &mut self, addresses: impl IntoIterator<Item = Address>, ) -> usize
Purge storage slots for multiple contracts from both cache layers.
See purge_contract_storage() for details on what each layer contains.
Sourcepub fn purge_all_storage(&mut self) -> usize
pub fn purge_all_storage(&mut self) -> usize
Purge ALL storage slots from both cache layers while preserving bytecodes.
Use this for periodic full cache refresh (e.g., every 48 hours) to ensure any stale data like strategy swap paths, proxy implementations, reward rates, etc. are re-fetched from the actual on-chain state.
This preserves:
- Account info (nonce, balance, code hash)
- Contract bytecodes (immutable)
This purges:
- All storage slots from CacheDB overlay (layer 1)
- All storage slots from BlockchainDb backend (layer 2)
§Returns
The total number of storage slots that were removed from the BlockchainDb
Sourcepub fn enumerate_contract_slots(&self, address: Address) -> Vec<U256> ⓘ
pub fn enumerate_contract_slots(&self, address: Address) -> Vec<U256> ⓘ
Enumerate all cached storage slots for a contract address.
Returns the union of slot keys from both CacheDB overlay (layer 1) and BlockchainDb backend (layer 2). Used by the slot observation tracker to selectively purge only slots likely to have changed.
Sourcepub fn all_cached_contract_addresses(&self) -> Vec<Address>
pub fn all_cached_contract_addresses(&self) -> Vec<Address>
Return all contract addresses that have cached storage in either layer.
Used by the observation-aware full purge to enumerate what needs checking.
Sourcepub fn cache_db_storage_slot_count(&self, address: Address) -> usize
pub fn cache_db_storage_slot_count(&self, address: Address) -> usize
Get the number of storage slots in the CacheDB overlay for a contract.
This is useful for diagnostics: if a contract has slots in the CacheDB overlay, they will be served on EVM reads without going to the backend.
Sourcepub fn simulate_call_with_balance_deltas(
&mut self,
from: Address,
to: Address,
calldata: Bytes,
owner: Address,
tokens: impl IntoIterator<Item = Address>,
commit: bool,
) -> Result<CallSimulationResult>
pub fn simulate_call_with_balance_deltas( &mut self, from: Address, to: Address, calldata: Bytes, owner: Address, tokens: impl IntoIterator<Item = Address>, commit: bool, ) -> Result<CallSimulationResult>
Simulate a call and compute owner’s net balance change for each token
in tokens by reading balanceOf(owner) immediately before and after.
Each delta is the signed post - pre difference (see
CallSimulationResult::token_deltas). When commit is true the call’s
state changes are persisted to the CacheDB overlay; otherwise they are
reverted. Unlike
simulate_with_transfer_tracking,
this measures deltas via pre/post balance reads (not transfer-event
inspection). The returned access_list
includes the accounts and slots touched by the pre/post balanceOf reads
and the simulated call.
§Errors
Returns an error if building the tx env fails, if a pre/post
balanceOf read fails, or if the call does not Success (i.e. it
reverted or halted). On error the simulation is reverted.
Sourcepub fn simulate_with_transfer_tracking(
&mut self,
from: Address,
to: Address,
calldata: Bytes,
owner: Address,
tokens: Option<impl IntoIterator<Item = Address>>,
commit: bool,
) -> SimulationResult<CallSimulationResult>
pub fn simulate_with_transfer_tracking( &mut self, from: Address, to: Address, calldata: Bytes, owner: Address, tokens: Option<impl IntoIterator<Item = Address>>, commit: bool, ) -> SimulationResult<CallSimulationResult>
Simulate a call and track token balance changes using a TransferInspector.
This method uses EVM inspection to capture ERC20 Transfer events during execution, eliminating the need for manual balance reads before/after the transaction.
Returns:
Ok(CallSimulationResult)on successful executionErr(SimError::Revert(_))when the transaction reverts (graceful failure)Err(SimError::Other(_))for unexpected errors (should be propagated)
Sourcepub fn simulate_with_transfer_tracking_raw(
&mut self,
from: Address,
to: Address,
calldata: Bytes,
owner: Address,
tokens: Option<impl IntoIterator<Item = Address>>,
commit: bool,
) -> SimulationResult<CallSimulationResult>
pub fn simulate_with_transfer_tracking_raw( &mut self, from: Address, to: Address, calldata: Bytes, owner: Address, tokens: Option<impl IntoIterator<Item = Address>>, commit: bool, ) -> SimulationResult<CallSimulationResult>
Simulate a call with transfer tracking without any prefetching.
This is identical to simulate_with_transfer_tracking since we no longer
do access list prefetching. Kept for API compatibility.
Sourcepub fn simulate_bundle(
&mut self,
txs: &[BundleTx],
opts: &BundleOptions,
) -> SimulationResult<BundleResult>
pub fn simulate_bundle( &mut self, txs: &[BundleTx], opts: &BundleOptions, ) -> SimulationResult<BundleResult>
Simulate an ordered transaction bundle over cumulative block state, with a revert policy and coinbase/miner-payment accounting (Phase 6 Track A+B).
This is a convenience wrapper: it snapshots the cache and runs the bundle
on a fresh transient EvmOverlay via
EvmOverlay::simulate_bundle,
which carries the full semantics (ordered cumulative state, the
RevertPolicy, and coinbase accounting).
The cache itself is never mutated — even when opts.commit is true.
commit controls only whether the bundle’s cumulative state is folded
into the transient overlay (and is therefore moot here, since that overlay
is dropped when this call returns). Snapshot the cache yourself and drive
EvmOverlay::simulate_bundle directly when you need the committed
overlay state to outlive the call (e.g. to chain a follow-up read).
§Errors
Returns SimError if a transaction environment cannot be built or revm
fails to transact. A transaction reverting is reported through the
per-transaction outcome and the revert policy, not as an error.
Sourcepub fn deploy_contract(
&mut self,
from: Address,
creation_code: Bytes,
) -> Result<Address>
pub fn deploy_contract( &mut self, from: Address, creation_code: Bytes, ) -> Result<Address>
Deploy a contract via CREATE transaction and return the deployed address.
The creation_code should include the init code with ABI-encoded constructor
arguments appended. Nonce checks are disabled, so any from address works.
Note: This commits the deployment to the CacheDB. Use a throw-away deployer
address (e.g., Address::ZERO) to avoid side effects on real accounts.
§Errors
Returns an error if the CREATE tx env cannot be built, if the deployment reverts or halts, or if it succeeds but the EVM returns no contract address.
Sourcepub fn override_account_code(
&mut self,
source: Address,
target: Address,
) -> Result<()>
pub fn override_account_code( &mut self, source: Address, target: Address, ) -> Result<()>
Override the bytecode at target address with bytecode from source address.
Copies only non-empty runtime code and code_hash; storage, balance, and nonce
at target remain unchanged. target must already have non-empty runtime
bytecode. Both the CacheDB overlay and BlockchainDb backend are updated,
ensuring the override is visible to parallel EVM tasks sharing the same backend.
§Errors
Returns an error if source has no cached bytecode or its code is empty,
if target cannot be loaded (it must already exist on the backend), or
if target has no existing runtime bytecode to override. For synthetic
target addresses that may not exist, use
override_or_create_account_code.
Sourcepub fn override_or_create_account_code(
&mut self,
source: Address,
target: Address,
) -> Result<()>
pub fn override_or_create_account_code( &mut self, source: Address, target: Address, ) -> Result<()>
Override the bytecode at target, creating a default target account when absent.
Use this for synthetic addresses in local simulations. For live forked
accounts where storage/balance/nonce must be preserved, prefer
Self::override_account_code.
Sourcepub fn override_account_code_with_missing_target(
&mut self,
source: Address,
target: Address,
missing_target: MissingTargetBehavior,
) -> Result<()>
pub fn override_account_code_with_missing_target( &mut self, source: Address, target: Address, missing_target: MissingTargetBehavior, ) -> Result<()>
Override code at target, with explicit behavior for missing target accounts.
This is intentionally not folded onto
apply_update’s Account code patch: it copies code
from a source account, preserves the target’s existing balance/nonce/
storage, and unconditionally materializes the target in the CacheDB
overlay (the primary read path for EVM execution, required for the
Create synthetic-target case). The generic primitive writes the overlay
only when an account is already present, so the two are not
behavior-equivalent. For a plain code overwrite that follows the
dual-layer write-through policy, use
apply_update(StateUpdate::Account { patch: AccountPatch::default().code(..) }).
Source§impl EvmCache
impl EvmCache
Sourcepub fn execute_cold_start_round(&mut self, plan: &ColdStartPlan) -> RoundOutcome
pub fn execute_cold_start_round(&mut self, plan: &ColdStartPlan) -> RoundOutcome
Execute a single cold-start round and return its (possibly partial) outcome.
Fixed phase order: accounts → verify → probe → discover.
Per-round fetcher guard: if the plan declares any verify or probe slots and
the cache has no storage batch fetcher, the round short-circuits with
ColdStartError::NoBatchFetcher before issuing any read. A round
declaring only accounts/discover runs without a fetcher.
- accounts (first): each
plan.accountsaddress is pre-seeded viaensure_account_blocking. A failure here is a hard error in the first phase, so nothing after it ran: every declared verify/probe slot is markedSlotFetch::NotAttemptedand the round returns witherror: Some(..). This is the only producer ofNotAttempted. - verify: each verify slot is re-fetched, classified into
results.fetched, and (when changed) injected and recorded inresults.verified. - probe: each probe slot is re-fetched at the pinned block and
classified into
results.probedvia the same sharedResult<U256>classification verify uses. Unlike verify, a probe injects nothing and records noSlotChange: it is the archive-miss classification for slots a consumer does not want to warm. - discover (last): each
ColdStartCallis executed viacall_raw_with_access_list, its access list filtered byrestrict_to, and the result pushed toresults.discovered. A discover failure preserves the verify/probe outcomes already computed this round (they ran earlier, so they are notNotAttempted); the failing call and all subsequent discover calls are dropped, and the round returns witherror: Some(..).
Sourcepub fn run_cold_start(
&mut self,
planner: &mut dyn ColdStartPlanner,
config: ColdStartConfig,
) -> Result<ColdStartRunReport, ColdStartError>
pub fn run_cold_start( &mut self, planner: &mut dyn ColdStartPlanner, config: ColdStartConfig, ) -> Result<ColdStartRunReport, ColdStartError>
Run a bounded multi-round cold start driven by planner.
Pin handling per config.pin: ColdStartPin::CachePinned is a no-op;
ColdStartPin::Hash pins every round to
BlockId::from((hash, Some(require_canonical))), capturing the prior block
and restoring it on every exit path (success, budget-exceeded, and
mid-round error).
The loop checks the round budget at the top: with max_rounds = N, rounds
0..N execute and a planner still returning Continue after round N
yields RoundBudgetExceeded. Each
round’s results are absorbed into the report before its error is
checked; a round error propagates after restoring the pin and without
calling on_results. Note the absorbed report is returned only on the
Ok path — on error it is dropped and the Err carries only the cause;
use execute_cold_start_round directly
to observe a failed round’s partial outcomes.
Trait Implementations§
Source§impl StateView for EvmCache
Read-only state view for the event pipeline (Pillar B.2): a decoder reads the
current cached value of a slot through cached_storage_value,
which never touches RPC and is account_state-aware (a cold slot reads
None).
impl StateView for EvmCache
Read-only state view for the event pipeline (Pillar B.2): a decoder reads the
current cached value of a slot through cached_storage_value,
which never touches RPC and is account_state-aware (a cold slot reads
None).
Auto Trait Implementations§
impl !RefUnwindSafe for EvmCache
impl !Send for EvmCache
impl !Sync for EvmCache
impl !UnwindSafe for EvmCache
impl Freeze for EvmCache
impl Unpin for EvmCache
impl UnsafeUnpin for EvmCache
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> FmtForward for T
impl<T> FmtForward for T
Source§fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
self to use its Binary implementation when Debug-formatted.Source§fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
self to use its Display implementation when
Debug-formatted.Source§fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
self to use its LowerExp implementation when
Debug-formatted.Source§fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
self to use its LowerHex implementation when
Debug-formatted.Source§fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
self to use its Octal implementation when Debug-formatted.Source§fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
self to use its Pointer implementation when
Debug-formatted.Source§fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
self to use its UpperExp implementation when
Debug-formatted.Source§fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
self to use its UpperHex implementation when
Debug-formatted.Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
Source§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
Source§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
Source§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
Source§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
self, then passes self.as_ref() into the pipe function.Source§fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
self, then passes self.as_mut() into the pipe
function.Source§fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
self, then passes self.deref() into the pipe function.Source§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
Source§impl<T> Tap for T
impl<T> Tap for T
Source§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Borrow<B> of a value. Read moreSource§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
BorrowMut<B> of a value. Read moreSource§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
AsRef<R> view of a value. Read moreSource§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
AsMut<R> view of a value. Read moreSource§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap() only in debug builds, and is erased in release builds.Source§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
.tap_borrow() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
.tap_borrow_mut() only in debug builds, and is erased in release
builds.Source§fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
.tap_ref() only in debug builds, and is erased in release
builds.Source§fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
.tap_ref_mut() only in debug builds, and is erased in release
builds.Source§fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
.tap_deref() only in debug builds, and is erased in release
builds.