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
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
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, RpcError>>
pub fn rpc_call( &self, to: Address, calldata: Bytes, ) -> Option<Result<Bytes, RpcError>>
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 account_proof_fetcher(&self) -> Option<&AccountProofFetchFn>
pub fn account_proof_fetcher(&self) -> Option<&AccountProofFetchFn>
Get the account/root proof fetcher, if available.
Returns None when constructed via from_backend (no provider
available) unless a fetcher was injected via
set_account_proof_fetcher.
§Panics
The returned AccountProofFetchFn must be invoked from within a
multi-thread tokio runtime: it drives eth_getProof 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
address rather than panicking, but block_in_place itself will panic if
invoked from a non-worker thread of a multi-thread runtime.
Sourcepub fn block_state_diff_fetcher(&self) -> Option<&BlockStateDiffFetchFn>
pub fn block_state_diff_fetcher(&self) -> Option<&BlockStateDiffFetchFn>
Get the block state-diff fetcher, if available.
Returns None when constructed via from_backend (no provider
available) unless a fetcher was injected via
set_block_state_diff_fetcher.
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 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 prewarm_slots(&mut self, requests: &[(Address, U256)]) -> PrewarmReport
pub fn prewarm_slots(&mut self, requests: &[(Address, U256)]) -> PrewarmReport
Bulk-load the given slots into the cache at its pinned block.
Fetches through the installed StorageBatchFetchFn — bulk eth_call
extraction by default, so thousands of slots (across many contracts)
arrive in a handful of calls — and injects every successfully fetched
value into layer 2 via
inject_storage_batch, the cold-prefetch
write. Use it to prewarm a declared working set (an AMM pool’s tick
range, a protocol’s config slots) before entering a simulation or
reactive loop, complementing the recorded working sets that
prefetch_registry replays.
Duplicate pairs are fetched once each and injected idempotently. Returns how many slots loaded and which pairs failed; failures leave the cache unchanged (those slots lazily point-read later as usual).
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.
Production callers can also inject their own transport, retry, batching,
or rate-limiting strategy here. Once replaced, the cache’s
StorageBatchConfig no longer controls batching; the custom fetcher is
responsible for honoring the StorageBatchFetchFn contract.
Sourcepub fn set_account_proof_fetcher(&mut self, f: AccountProofFetchFn)
pub fn set_account_proof_fetcher(&mut self, f: AccountProofFetchFn)
Set (or replace) the account/root proof fetcher.
This is the seam account-target resyncs and account-level freshness use to
drive eth_getProof fetches without a live provider: a stubbed
AccountProofFetchFn can be injected over a mocked-provider cache,
mirroring set_storage_batch_fetcher.
Sourcepub fn set_block_state_diff_fetcher(&mut self, f: BlockStateDiffFetchFn)
pub fn set_block_state_diff_fetcher(&mut self, f: BlockStateDiffFetchFn)
Set (or replace) the block state-diff fetcher.
This is the seam trace-backed reactive resync uses to resolve matching targets from one block-level debug trace before falling back to storage or account proof point reads.
Sourcepub fn set_account_fields_fetcher(&mut self, f: AccountFieldsFetchFn)
pub fn set_account_fields_fetcher(&mut self, f: AccountFieldsFetchFn)
Set (or replace) the bulk account-fields fetcher.
This is the seam verify_code_seeds (and
the cold-start verify_code phase) reads through: a stubbed
AccountFieldsFetchFn can be injected over a mocked-provider cache,
mirroring set_storage_batch_fetcher.
Sourcepub fn account_fields_fetcher(&self) -> Option<&AccountFieldsFetchFn>
pub fn account_fields_fetcher(&self) -> Option<&AccountFieldsFetchFn>
The installed bulk account-fields fetcher, if any.
Some on provider-backed caches (default-wired to
fetch_account_fields_bulk);
None on from_backend caches until one is
installed via
set_account_fields_fetcher.
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
snapshot / build_evm; existing
snapshots and overlays keep the chain ID captured when they were created.
Sourcepub fn checkpoint(&self) -> Cache
pub fn checkpoint(&self) -> Cache
Take a low-level, same-thread checkpoint 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 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, checkpoint: Cache)
pub fn restore(&mut self, checkpoint: Cache)
Restore the CacheDB overlay from a checkpoint taken with
checkpoint.
Overwrites the layer-1 overlay wholesale with checkpoint, discarding any
overlay mutations made since it was taken. The BlockchainDb backend is
untouched. This is the in-place counterpart to the cross-thread
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 snapshot(&mut self) -> Arc<EvmSnapshot> ⓘ
pub fn 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 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
checkpoint / restore instead.
Sourcepub fn invalidate_snapshot_base(&mut self)
pub fn invalidate_snapshot_base(&mut self)
Force the next 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 snapshot_generation(&self) -> u64
pub fn snapshot_generation(&self) -> u64
Monotonic generation counter for snapshot consistency (G6).
Increments on every targeted state write (apply_update,
apply_updates, modify_slot
— and therefore everything built on them: reactive ingestion, freshness
corrections, fresh injections) and on block re-pins
(set_block, advance_block).
Cold prefetch (inject_storage_batch) and
lazy backend fetches do not increment it: they materialize the pinned
block’s existing state rather than changing it.
The magnitude is opaque — how much one call increments it is unspecified — so compare values for equality only.
The fan-out pattern: read the generation, take the
snapshot, read the generation again. If the two
reads differ, state mutated in between (e.g. your event loop applied
part of a block between the reads) — discard and re-snapshot to avoid
fanning out simulations over a mid-block state.
let snapshot = loop {
let generation = cache.snapshot_generation();
let snapshot = cache.snapshot();
if cache.snapshot_generation() == generation {
break snapshot;
}
// A mutation interleaved: try again at the next stable point.
};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 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 coinbase(&self) -> Option<Address>
pub fn coinbase(&self) -> Option<Address>
Get the block beneficiary used for EVM simulations (the COINBASE
opcode).
Fetched from the pinned block’s header at construction, refreshed by
advance_block, or overridden via
set_coinbase. None means revm uses its default
beneficiary.
Sourcepub fn prevrandao(&self) -> Option<B256>
pub fn prevrandao(&self) -> Option<B256>
Get prevrandao used for EVM simulations (the PREVRANDAO opcode, the
post-merge header mix hash).
Fetched from the pinned block’s header at construction, refreshed by
advance_block, or overridden via
set_prevrandao. None leaves revm’s default in
place.
Sourcepub fn block_gas_limit(&self) -> Option<u64>
pub fn block_gas_limit(&self) -> Option<u64>
Get the block gas limit used for EVM simulations (the GASLIMIT opcode).
Fetched from the pinned block’s header at construction, refreshed by
advance_block, or overridden via
set_block_gas_limit. None lets revm use
its default.
Sourcepub fn set_block_context_requirements(&mut self, reqs: BlockContextRequirements)
pub fn set_block_context_requirements(&mut self, reqs: BlockContextRequirements)
Set which block-context header fields subsequent
advance_block calls require to be present.
See BlockContextRequirements. Under
strict enforcement,
advance_block rejects a header missing a required
field rather than silently defaulting it.
Sourcepub fn advance_block<H: BlockHeader>(
&mut self,
header: &H,
) -> Result<(), BlockContextError>
pub fn advance_block<H: BlockHeader>( &mut self, header: &H, ) -> Result<(), BlockContextError>
Engine-driven per-block env refresh from a canonical block header.
First validates the header against the configured
BlockContextRequirements (set via
set_block_context_requirements
or the strict builder path). Under strict/partial requirements a header
missing a required field is rejected with BlockContextError instead of
being silently defaulted; under the lenient
default this never errors.
On success it refreshes the full EVM block env from the header — block
number (NUMBER), base fee (BASEFEE), beneficiary (COINBASE),
prevrandao (PREVRANDAO), gas limit (GASLIMIT) and timestamp — and
re-pins every RPC fetch path (the SharedBackend lazy fallback, the
batch storage fetcher, and the account-proof fetcher) to the header’s
block number, so a lazy miss after the advance reads state at the
advanced block, in lockstep with the env. Intended to be driven once per
canonical block (e.g. by the reactive runtime as new headers arrive).
Unlike set_block, this does not invalidate the
memoized COW snapshot base: an advance is a forward roll of the same live
view (canonical mutations flow through the write funnel, which already
marks the base dirty; lazy fetches stay insert-only-on-miss, which the
base’s growth scan catches), whereas set_block is a wholesale re-fork
that must rebuild layer 2. Re-pinning to an older block is a re-fork,
not an advance — use set_block for that.
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 discovery pass for that token and write the balance directly.
Seeded slots are assumed to use Solidity’s keccak(key‖slot) layout
(use seed_erc20_balance_layouts for
Vyper/Solady tokens). Seeding a wrong slot is self-correcting: the write
is verified and a fresh discovery pass runs (evicting the bad seed) if it
fails. Later entries overwrite earlier ones for the same token.
Sourcepub fn seed_erc20_balance_layouts(
&mut self,
mappings: impl IntoIterator<Item = TrackedMapping>,
)
pub fn seed_erc20_balance_layouts( &mut self, mappings: impl IntoIterator<Item = TrackedMapping>, )
Pre-seed known ERC20 balance mapping descriptors (base slot and
layout), keyed by TrackedMapping::contract.
The layout-aware companion to
seed_erc20_balance_slots: use this for
Vyper (keccak(slot‖key)) or Solady (packed) tokens whose layout is known
up front, so set_erc20_balance_with_slot_scan
writes the correct slot without a discovery pass.
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 call_raw_with_inspector<I>(
&mut self,
from: Address,
to: Address,
calldata: Bytes,
tx: &TxConfig,
inspector: I,
) -> Result<(ExecutionResult, I)>where
I: for<'a> Inspector<Context<BlockEnv, TxEnv, CfgEnv, &'a mut ForkCacheDB, Journal<&'a mut ForkCacheDB>, ()>>,
pub fn call_raw_with_inspector<I>(
&mut self,
from: Address,
to: Address,
calldata: Bytes,
tx: &TxConfig,
inspector: I,
) -> Result<(ExecutionResult, I)>where
I: for<'a> Inspector<Context<BlockEnv, TxEnv, CfgEnv, &'a mut ForkCacheDB, Journal<&'a mut ForkCacheDB>, ()>>,
Run a call with a composable revm::Inspector attached, without
committing state (the journal is reverted after execution), returning
the execution result and the inspector moved back out so you can read what
it captured.
This is the cache-level counterpart to
EvmOverlay::call_raw_with_inspector.
Unlike the overlay form it runs directly against the cache’s database, so
any missing state is fetched lazily during the call — discovery works on a
cold fork with no pre-warming.
Sourcepub fn trace_hashed_slots(
&mut self,
from: Address,
to: Address,
calldata: Bytes,
known_keys: &[B256],
) -> Result<Vec<HashSlotAccess>>
pub fn trace_hashed_slots( &mut self, from: Address, to: Address, calldata: Bytes, known_keys: &[B256], ) -> Result<Vec<HashSlotAccess>>
Discover every hash-derived storage slot a call reads, factored into
HashSlotAccesses (mapping keys, base slot, layout, exact slot, value).
This is the general, ERC-20-agnostic entry point: it works for any mapping
(balances, allowances, protocol positions, …) and any layout
(Solidity / Vyper / Solady / nested), in a single simulation.
known_keys are words — typically addresses via Address::into_word —
used to anchor key/slot disambiguation; pass &[] to rely on the
magnitude heuristic.
§Limitations
Discovery only sees slots the call actually SLOADs. A getter that
returns a computed value without reading a per-key backing slot — a
rebasing balance derived from shares (stETH, Aave aTokens), or a value
served purely from memory/calldata — yields no matching access. Callers
that need a slot regardless (e.g. set_erc20_balance_with_slot_scan)
fall through to a brute-force fallback rather than acting on a wrong slot.
Sourcepub fn discover_erc20_balance_slot(
&mut self,
token: Address,
owner: Address,
) -> Result<Option<HashSlotAccess>>
pub fn discover_erc20_balance_slot( &mut self, token: Address, owner: Address, ) -> Result<Option<HashSlotAccess>>
Discover a token’s balanceOf mapping slot for owner from a single
simulated call — layout-agnostic (Solidity / Vyper / Solady), with no
max_slot bound and no repeated probing.
Returns the HashSlotAccess whose loaded value matches the getter’s
return and whose key is owner, or None if the token exposes no hashed
balance read — a rebasing/computed getter that does not SLOAD a
per-owner slot (stETH, aTokens), or unavailable code. Capture the
result with HashSlotAccess::as_tracked to reuse the layout for other
holders without re-simulating.
Sourcepub fn track_erc20_balances(
&mut self,
token: Address,
holders: impl IntoIterator<Item = Address>,
) -> Result<Option<TrackedBalances>>
pub fn track_erc20_balances( &mut self, token: Address, holders: impl IntoIterator<Item = Address>, ) -> Result<Option<TrackedBalances>>
Derive a token’s balance mapping layout once, then compute the exact
storage slot for each of holders — the “discover, then track these
addresses” primitive.
Reuses a cached/seeded TrackedMapping for token if present;
otherwise discovers it from a single balanceOf simulation (using the
first holder as the probe) and caches it. Returns the reusable descriptor
plus (holder, slot) pairs — feed the slots to a
FreshnessRegistry
(pin_slot/mark_volatile_slot) or a
PrefetchRegistry to keep
them warm and fresh. Returns None if the layout can’t be discovered
(e.g. an empty holders set with no cached descriptor, or a token with no
hashed balance read).
Sourcepub fn set_erc20_allowance(
&mut self,
token: Address,
owner: Address,
spender: Address,
amount: U256,
) -> Result<bool>
pub fn set_erc20_allowance( &mut self, token: Address, owner: Address, spender: Address, amount: U256, ) -> Result<bool>
Forge an ERC-20 allowance: discover the (nested) allowance mapping entry
for (owner, spender) from a single traced allowance call, write
amount to the exact slot, and verify.
This is the approval counterpart to
set_erc20_balance_with_slot_scan
— newly feasible because nested-mapping discovery can locate
keccak(spender ‖ keccak(owner ‖ base)) (and its Vyper/packed variants)
without a scan. Pass U256::MAX for an “unlimited” approval.
Returns Ok(true) if set and verified, Ok(false) if the token exposes
no discoverable hashed allowance entry keyed by (owner, spender).
Sourcepub fn write_mapping_entry(
&mut self,
tracked: &TrackedMapping,
key: B256,
value: U256,
) -> Result<B256>
pub fn write_mapping_entry( &mut self, tracked: &TrackedMapping, key: B256, value: U256, ) -> Result<B256>
Write value into a mapping entry using a discovered
TrackedMapping layout, returning the exact storage slot written.
Unlike insert_mapping_storage_slot,
which always assumes Solidity keccak(key‖slot) order, this honors the
tracked layout, so it writes the correct slot for Vyper and Solady tokens
too. The (contract, layout, base slot) all come from the tracked
descriptor.
Sourcepub fn mock_overlay(&mut self) -> EvmOverlay
pub fn mock_overlay(&mut self) -> EvmOverlay
Create a throwaway EvmOverlay over the current snapshot, wired to this
cache’s backend for lazy fetch.
This is the entry point for overlay-scoped mocking: mock balances,
approvals, and getter returns on the returned overlay
(EvmOverlay::mock_balance, EvmOverlay::mock_allowance,
EvmOverlay::mock_call) and run your simulations on that overlay. The
mocks live only in the overlay’s dirty layer and are dropped with it — the
cache is never mutated, so a mocked balance can’t leak into a later
simulation. (For a persistent cache-level balance override, use
set_erc20_balance_with_slot_scan.)
let mut sim = cache.mock_overlay();
sim.mock_balance(usdc, alice, U256::from(1_000_000_000_000u64))?; // 1M USDC (6 dp)
sim.mock_allowance(usdc, alice, router, U256::MAX)?; // unlimited approve
let out = sim.call_raw(alice, router, swap)?; // simulate against the mocked state
// drop `sim` → mocks discarded; `cache` is untouched.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, discovering the token’s balance mapping slot and
layout if not already known, then writing amount there.
Resolution order:
- A cached/seeded
TrackedMappingfor the token (fast path). - Trace-based discovery — a single simulated
balanceOf(owner), layout-agnostic (Solidity / Vyper / Solady) and unbounded bymax_slot(seediscover_erc20_balance_slot). - The legacy brute-force scan of
0..=max_slot(Solidity order only), kept as a fallback for the rare token whosebalanceOfreads no hashed slot the trace can attribute.
Every path verifies the write via balanceOf before caching, so a wrong
guess is self-correcting. Returns Ok(true) if set and verified,
Ok(false) if nothing worked, 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 snapshot copies it onto the snapshot
so snapshot-backed EvmOverlays pre-allocate the same amount.
Sourcepub fn storage_batch_config(&self) -> StorageBatchConfig
pub fn storage_batch_config(&self) -> StorageBatchConfig
The cache-side storage batch-fetch configuration for this instance.
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(..) }).
Sourcepub fn verify_code_seeds(&mut self) -> Result<CodeVerifyReport>
pub fn verify_code_seeds(&mut self) -> Result<CodeVerifyReport>
Verify every CodeSeedState::Pending canonical code claim against
the chain at the pinned block — one bulk eth_call for the whole set.
Per-address outcomes (see CodeVerifyReport):
- match ⇒ marked
CodeSeedState::Verified(never re-checked; post-EIP-6780 code is immutable) and the account’s real balance is patched in from the same response — pure materialization of pinned-block truth, so it does not bump the snapshot generation; - mismatch / not-deployed / code-less ⇒
purge_account(both layers and the mark; the purge path bumps the generation) — the next touch refetches authoritative chain state; - transport failure (the whole call, an omitted address, or the
MULTICALL3_ADDRESSextractor-host caveat) ⇒ stillPending, reportedunverifiable— a failed read proves nothing, so it never promotes and never destroys a seed.
With no pending seeds this is a no-op that needs no fetcher. Verified seeds are skipped forever, so calling this repeatedly (or from every cold-start round) costs nothing once the set is settled.
§Errors
CacheError::MissingAccountFieldsFetcher when pending seeds exist
but no AccountFieldsFetchFn is installed (a
from_backend cache without
set_account_fields_fetcher).
Sourcepub fn seed_account_code(
&mut self,
address: Address,
code: Bytes,
) -> Result<B256>
pub fn seed_account_code( &mut self, address: Address, code: Bytes, ) -> Result<B256>
Seed canonical runtime code for address without fetching it.
The claim is marked CodeSeedState::Pending until
verify_code_seeds confirms it against the
on-chain EXTCODEHASH (or the cold-start driver’s verify_code phase
does). Because the account is materialized in both cache layers, the
lazy backend never fires its balance/nonce/code RPC triple for it.
Defaults: nonce 1 (the EIP-161 contract minimum — exact for any
contract that never CREATEs) and balance ZERO until verification
patches the real value from the same response. Use
seed_account_code_with to supply
both explicitly.
Conflict rules (chain-fetched state is authoritative over templates):
seeding an unmarked address that already holds RPC-origin code
with the same hash marks it Verified immediately (zero RPC — the
warm-cache fast path); a differing hash (including a code-less EOA) is
CacheError::CodeSeedConflict and leaves the cached code untouched.
Re-seeding a marked address overwrites and restarts the claim as
Pending.
Returns the keccak256 hash recorded for the claim.
§Errors
CacheError::CodeSeedEmpty for empty code;
CacheError::CodeSeedConflict as above.
Sourcepub fn seed_account_code_with(
&mut self,
address: Address,
code: Bytes,
nonce: u64,
balance: U256,
) -> Result<B256>
pub fn seed_account_code_with( &mut self, address: Address, code: Bytes, nonce: u64, balance: U256, ) -> Result<B256>
seed_account_code with explicit nonce
and provisional balance for the materialized account. Verification
still overwrites the balance with the on-chain value on a match; the
nonce keeps the supplied value (an exact nonce needs the
eth_getProof path and only matters for contracts that CREATE).
Sourcepub fn etch_account_code(
&mut self,
address: Address,
code: Bytes,
) -> Result<B256>
pub fn etch_account_code( &mut self, address: Address, code: Bytes, ) -> Result<B256>
Etch deliberately-local runtime code at address — the raw-bytes
sibling of override_or_create_account_code,
with no source account needed.
Marks CodeSeedState::Etched: never verified, excluded from all
canonical machinery, and reported via
etched_accounts so local divergence stays
visible. Preserves the existing balance/nonce/storage when the account
is already present; creates a default account otherwise. Overwrites
any prior code or mark — divergence is the caller’s explicit intent.
Returns the keccak256 hash of the etched code.
§Errors
CacheError::CodeSeedEmpty for empty code.
Sourcepub fn code_seed_state(&self, address: &Address) -> Option<&CodeSeedState>
pub fn code_seed_state(&self, address: &Address) -> Option<&CodeSeedState>
The code-seed mark for address, if any. None means RPC-origin:
the code (if present) was fetched from the provider and is trusted as
chain state.
Sourcepub fn pending_code_seeds(&self) -> Vec<Address>
pub fn pending_code_seeds(&self) -> Vec<Address>
Addresses whose canonical code claims still await verification
(CodeSeedState::Pending), sorted for deterministic iteration.
This is the implicit work set of
verify_code_seeds and the cold-start
verify_code phase.
Sourcepub fn etched_accounts(&self) -> Vec<Address>
pub fn etched_accounts(&self) -> Vec<Address>
Addresses whose code deliberately diverges from the chain
(CodeSeedState::Etched), sorted for deterministic iteration. This
is the health surface for local divergence: everything written through
etch_account_code,
override_account_code and friends, or
deploy_contract appears here.
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: verify_code → accounts → verify → probe → probe_roots → discover.
Per-round fetcher guards: 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; likewise a
probe_roots-bearing round with no account proof fetcher short-circuits
with ColdStartError::NoAccountProofFetcher, and a round with pending
code seeds but no account-fields fetcher short-circuits with
ColdStartError::NoAccountFieldsFetcher. A round declaring only
accounts/discover (and holding no pending seeds) runs without any
fetcher.
- verify_code (first): every
CodeSeedStatePendingclaim is settled viaverify_code_seeds; the work set is the cache’s own pending marks, not a plan field. Matches are markedVerified; contradicted claims are purged, so an address also listed inplan.accountsis refetched by the very next phase. A transport failure is not a round hard error — it surfaces in the report’sunverifiablebucket (matching probe_roots’ per-address stance). Running first means no discover sim ever executes over an unverified claim, andresults.code_verificationssurvives any later phase’s hard error. With no pending seeds the phase is a no-op (code_verifications: None). - accounts: each
plan.accountsaddress is pre-seeded viaensure_account_blocking. A failure here is a hard error before the slot phases ran: every declared verify/probe slot is markedSlotFetch::NotAttempted, every declared probe_roots address is synthesized asroot: None, and the round returns witherror: Some(..)(the already-computedcode_verificationsis preserved). 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. - probe_roots: each
plan.probe_rootsaddress is root-only probed ((addr, vec![])) through the account proof fetcher at the pinned block and recorded intoresults.probed_rootsas aRootProbeOutcome. Nothing is injected; a per-address failure (or an address the fetcher omitted) isroot: None, never a round hard error. - 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.