Skip to main content

evm_fork_cache/cache/
overlay.rs

1//! Per-simulation state overlays layered over a snapshot or the live cache.
2//!
3//! An [`EvmOverlay`] wraps a read-only base (an
4//! [`EvmSnapshot`] or the cache itself) with
5//! a scratch write layer, so a simulation can mutate balances, storage, and code
6//! and run calls without disturbing the base or other overlays. Overlays are the
7//! `Send` unit of parallel fan-out: snapshot once, clone cheaply, overlay per
8//! candidate. Reads fall through the write layer to the base; writes and reverts
9//! stay local to the overlay.
10
11use std::cell::RefCell;
12use std::collections::{HashMap, HashSet};
13use std::rc::Rc;
14use std::sync::Arc;
15
16use alloy_eips::eip2930::{AccessList, AccessListItem};
17use alloy_primitives::{Address, B256, Bytes, TxKind, U256};
18use foundry_fork_db::{DatabaseError, SharedBackend};
19use revm::{
20    Context, ExecuteCommitEvm, ExecuteEvm, InspectEvm, MainBuilder, MainContext,
21    context::{BlockEnv, CfgEnv, Journal, LocalContext, TxEnv, result::ExecutionResult},
22    database_interface::{Database, DatabaseRef},
23    state::{AccountInfo, Bytecode},
24};
25
26use super::snapshot::EvmSnapshot;
27use super::{CallSimulationResult, IERC20, SimStatus, TxConfig, unix_timestamp_secs_saturating};
28use crate::access_set::StorageAccessList;
29use crate::bundle::{BundleOptions, BundleResult, BundleTx, RevertPolicy, TxOutcome};
30use crate::cancellation::SimulationCancellationToken;
31use crate::errors::{
32    OverlayError, OverlayResult as Result, SimError, SimHostError, SimulationError,
33    SimulationResult,
34};
35use crate::inspector::TransferInspector;
36use crate::mapping_probe::HashStorageProbe;
37use alloy_sol_types::SolCall;
38
39#[derive(Clone, Debug)]
40struct SimulationCancellationInspector {
41    token: SimulationCancellationToken,
42}
43
44impl<CTX, INTR> revm::Inspector<CTX, INTR> for SimulationCancellationInspector
45where
46    INTR: revm::interpreter::InterpreterTypes,
47{
48    fn step(&mut self, interpreter: &mut revm::interpreter::Interpreter<INTR>, _context: &mut CTX) {
49        self.token.mark_started();
50        if self.token.is_cancelled() {
51            interpreter.halt(revm::interpreter::InstructionResult::Stop);
52        }
53    }
54}
55
56type OverlayEvm<'a> = revm::MainnetEvm<
57    Context<BlockEnv, TxEnv, CfgEnv, &'a mut EvmOverlay, Journal<&'a mut EvmOverlay>, ()>,
58>;
59
60type InspectorOverlayEvm<'a, INSP> = revm::MainnetEvm<
61    Context<BlockEnv, TxEnv, CfgEnv, &'a mut EvmOverlay, Journal<&'a mut EvmOverlay>, ()>,
62    INSP,
63>;
64
65/// State an RPC-disconnected overlay could not resolve from its immutable
66/// snapshot.
67///
68/// The EVM database interface requires a value even when an offline snapshot is
69/// incomplete. [`EvmOverlay`] continues to return the protocol-neutral fallback
70/// (`None`, empty bytecode, ZERO storage, or ZERO block hash), but records every
71/// such fallback here. Callers must treat a non-empty report as an incomplete
72/// simulation rather than authoritative execution.
73#[derive(Clone, Debug, Default, PartialEq, Eq)]
74pub struct MissingState {
75    /// Account headers absent from the snapshot.
76    pub accounts: HashSet<Address>,
77    /// Runtime-code hashes absent from the snapshot.
78    pub code_hashes: HashSet<B256>,
79    /// Storage slots absent from the snapshot.
80    pub storage: HashSet<(Address, U256)>,
81    /// In-range block numbers whose hashes were absent from the snapshot.
82    pub block_hashes: HashSet<u64>,
83}
84
85impl MissingState {
86    /// Whether the offline overlay resolved every database read locally.
87    pub fn is_empty(&self) -> bool {
88        self.accounts.is_empty()
89            && self.code_hashes.is_empty()
90            && self.storage.is_empty()
91            && self.block_hashes.is_empty()
92    }
93
94    /// Convert unresolved reads into the generic execution read-set shape.
95    pub fn as_read_set(&self) -> StorageAccessList {
96        StorageAccessList {
97            accounts: self.accounts.clone(),
98            code_hashes: self.code_hashes.clone(),
99            slots: self.storage.clone(),
100            block_numbers: self.block_hashes.clone(),
101        }
102    }
103}
104
105/// Per-simulation mutable overlay on an immutable snapshot.
106///
107/// Lookup order: dirty layer → snapshot → ext_db (optional RPC fallback).
108///
109/// This type is `Send` (unlike `EvmCache`) because it uses no `Rc`/`RefCell`.
110/// Each simulation task gets its own `EvmOverlay` with a cheap `Arc::clone`
111/// of the shared `EvmSnapshot`.
112///
113/// # Reuse across simulations (Pillar A.2)
114///
115/// A worker doing many sims against the same snapshot can call [`Self::new`]
116/// once and [`Self::reset`] between sims instead of allocating a fresh overlay
117/// each time. The reusable shared-memory buffer is also recycled across calls —
118/// see [`Self::call_raw`] — without making the overlay `!Send`.
119pub struct EvmOverlay {
120    snapshot: Arc<EvmSnapshot>,
121    /// Per-simulation mutations (accounts fetched from ext_db, committed changes).
122    dirty_accounts: HashMap<Address, AccountInfo>,
123    /// Per-simulation storage mutations.
124    dirty_storage: HashMap<Address, HashMap<U256, U256>>,
125    /// Optional RPC fallback for data not in snapshot.
126    ext_db: Option<SharedBackend>,
127    /// Reusable shared-memory buffer, recycled across the build→transact→revert
128    /// call methods to avoid reallocating a 64 KB `Vec` per call.
129    ///
130    /// Stored as a plain `Vec<u8>` (not an `Rc`) so the overlay stays `Send`. A
131    /// call method `mem::take`s it, wraps it in a method-local `Rc<RefCell<_>>`
132    /// for revm's [`LocalContext`], runs, then reclaims and clears it after the
133    /// EVM is dropped (see [`Self::build_evm_with_local`]).
134    reusable_buffer: Vec<u8>,
135    /// Target pre-allocation (bytes) for [`Self::reusable_buffer`] and each
136    /// per-call buffer, taken from the snapshot's configured
137    /// [`SharedMemoryCapacity`](super::SharedMemoryCapacity) so overlays honor the
138    /// capacity set on the originating [`EvmCache`].
139    buffer_capacity: usize,
140    /// Set when a `BLOCKHASH` read fell through to the ZERO fallback (no
141    /// snapshot-provided hash and no `ext_db`). The freshness validator reads
142    /// this via [`Self::blockhash_zero_fallback`] to fail closed instead of
143    /// confirming a sim whose control flow may rest on a hash its overlays
144    /// cannot resolve. Cleared by [`Self::reset`].
145    blockhash_zero_fallback: bool,
146    /// Exact unresolved reads observed while no external database was attached.
147    missing_state: MissingState,
148}
149
150impl EvmOverlay {
151    /// Create a new overlay on the given snapshot.
152    ///
153    /// The reusable shared-memory buffer is pre-allocated to the snapshot's
154    /// configured shared-memory capacity (see
155    /// [`SharedMemoryCapacity`](super::SharedMemoryCapacity)).
156    pub fn new(snapshot: Arc<EvmSnapshot>, ext_db: Option<SharedBackend>) -> Self {
157        let buffer_capacity = snapshot.shared_memory_capacity;
158        Self {
159            snapshot,
160            dirty_accounts: HashMap::new(),
161            dirty_storage: HashMap::new(),
162            ext_db,
163            reusable_buffer: Vec::with_capacity(buffer_capacity),
164            buffer_capacity,
165            blockhash_zero_fallback: false,
166            missing_state: MissingState::default(),
167        }
168    }
169
170    /// Clear the per-simulation dirty layer so this overlay can be reused for the
171    /// next simulation against the same snapshot, without reallocating (Pillar
172    /// A.2).
173    ///
174    /// A worker doing K sims calls [`Self::new`] once and `reset()` between sims
175    /// instead of allocating a fresh overlay (plus dirty maps plus an `Arc`
176    /// clone) each time. After `reset()` the overlay reads the pristine snapshot
177    /// again — it is exactly equivalent to a freshly-built overlay on the same
178    /// snapshot. The snapshot `Arc`, the optional `ext_db`, and the reusable
179    /// shared-memory buffer (kept at capacity) are retained.
180    pub fn reset(&mut self) {
181        self.dirty_accounts.clear();
182        self.dirty_storage.clear();
183        self.blockhash_zero_fallback = false;
184        self.missing_state = MissingState::default();
185        // Keep: snapshot Arc, ext_db, and the reusable buffer. The buffer is
186        // already cleared after each call, so nothing to do for it here.
187    }
188
189    /// `true` if any `BLOCKHASH` read on this overlay fell through to the ZERO
190    /// fallback (no snapshot-provided hash for that number and no `ext_db`)
191    /// since construction or the last [`reset`](Self::reset).
192    ///
193    /// The freshness validator uses this to **fail closed**: a sim that read a
194    /// hash its ext-db-less overlays cannot resolve is reported
195    /// [`Unverified`](crate::freshness::Validation::Unverified) rather than
196    /// silently confirmed against a ZERO stand-in.
197    ///
198    /// Only reads revm actually routes to the database can set this: requests
199    /// outside the EVM's valid lookback window (`[current − 256, current)`)
200    /// return spec-mandated ZERO without a database call — that value is
201    /// correct on-chain too, so such reads are deliberately not flagged.
202    pub fn blockhash_zero_fallback(&self) -> bool {
203        self.blockhash_zero_fallback
204    }
205
206    /// Exact database reads that fell back because this overlay has no external
207    /// provider and its immutable snapshot did not contain the requested state.
208    ///
209    /// A non-empty report makes the simulation non-authoritative even when EVM
210    /// execution itself returned success: a missing storage slot, for example,
211    /// is represented as ZERO to satisfy the database trait.
212    pub fn missing_state(&self) -> &MissingState {
213        &self.missing_state
214    }
215
216    /// Chain ID of the block context captured by the underlying snapshot.
217    ///
218    /// This is the value installed into `cfg.chain_id` by [`Self::build_evm`].
219    pub fn chain_id(&self) -> u64 {
220        self.snapshot.chain_id
221    }
222
223    /// Block number of the snapshot's block context, or `None` if it was not
224    /// captured.
225    ///
226    /// When present this is the `block.number` simulations run against; when
227    /// `None`, [`Self::build_evm`] leaves revm's default block number in place.
228    pub fn block_number(&self) -> Option<u64> {
229        self.snapshot.block_number
230    }
231
232    /// Base fee of the snapshot's block context, or `None` if it was not
233    /// captured.
234    ///
235    /// Note that base-fee checks are disabled in the simulation EVM, so this is
236    /// informational rather than enforced against the transaction.
237    pub fn basefee(&self) -> Option<u64> {
238        self.snapshot.basefee
239    }
240
241    /// Timestamp of the snapshot's block context, or `None` if it was not
242    /// captured.
243    ///
244    /// When `None`, [`Self::build_evm`] substitutes the current wall-clock time
245    /// for `block.timestamp`.
246    pub fn timestamp(&self) -> Option<u64> {
247        self.snapshot.timestamp
248    }
249
250    /// A fresh [`LocalContext`] with a newly-allocated 64 KB shared-memory buffer.
251    ///
252    /// Used by the public [`Self::build_evm`], which hands out the EVM and cannot
253    /// reclaim its buffer afterwards. The internal call methods instead recycle
254    /// [`Self::reusable_buffer`] via [`Self::build_evm_with_local`].
255    fn fresh_local(&self) -> LocalContext {
256        LocalContext {
257            shared_memory_buffer: Rc::new(RefCell::new(Vec::with_capacity(self.buffer_capacity))),
258            precompile_error_message: None,
259        }
260    }
261
262    /// Build a revm EVM instance backed by this overlay, using a caller-supplied
263    /// [`LocalContext`].
264    ///
265    /// This is the shared body behind [`Self::build_evm`] and the internal call
266    /// methods. The call methods pass a `local` wrapping the recycled
267    /// [`Self::reusable_buffer`] (Pillar A.2) and reclaim it after the EVM is
268    /// dropped; [`Self::build_evm`] passes a fresh one.
269    ///
270    /// Note: the returned EVM is `!Send` (due to `LocalContext`'s `Rc<RefCell>`),
271    /// but this is fine because it's created and used within a single task.
272    fn build_evm_with_local(&mut self, local: LocalContext) -> OverlayEvm<'_> {
273        // Read snapshot values before the mutable borrow of self
274        let chain_id = self.snapshot.chain_id;
275        let spec_id = self.snapshot.spec_id;
276        let timestamp = self
277            .snapshot
278            .timestamp
279            .unwrap_or_else(|| unix_timestamp_secs_saturating(std::time::SystemTime::now()));
280        let block_number = self.snapshot.block_number;
281        let basefee = self.snapshot.basefee;
282        let coinbase = self.snapshot.coinbase;
283        let prevrandao = self.snapshot.prevrandao;
284        let gas_limit = self.snapshot.gas_limit;
285
286        let mut evm = Context::mainnet()
287            .with_db(&mut *self)
288            .with_local(local)
289            .modify_cfg_chained(|cfg| {
290                cfg.disable_nonce_check = true;
291                cfg.disable_eip3607 = true;
292                cfg.disable_base_fee = true;
293                cfg.disable_balance_check = true;
294                cfg.chain_id = chain_id;
295                cfg.limit_contract_code_size = None;
296                cfg.tx_chain_id_check = false;
297                cfg.spec = spec_id;
298            })
299            .build_mainnet();
300
301        evm.block.timestamp = U256::from(timestamp);
302        if let Some(number) = block_number {
303            evm.block.number = U256::from(number);
304        }
305        if let Some(basefee) = basefee {
306            evm.block.basefee = basefee;
307        }
308        if let Some(coinbase) = coinbase {
309            evm.block.beneficiary = coinbase;
310        }
311        if let Some(prevrandao) = prevrandao {
312            evm.block.prevrandao = Some(prevrandao);
313        }
314        if let Some(gas_limit) = gas_limit {
315            evm.block.gas_limit = gas_limit;
316        }
317        evm
318    }
319
320    /// Build a revm EVM instance backed by this overlay.
321    ///
322    /// This allocates a fresh 64 KB shared-memory buffer each call: it hands the
323    /// EVM out to the caller and cannot reclaim the buffer afterwards, so it
324    /// cannot recycle the overlay's reusable buffer. The internal call methods
325    /// ([`Self::call_raw`], etc.) recycle the buffer instead (Pillar A.2).
326    ///
327    /// Note: The returned EVM is `!Send` (due to `LocalContext`'s `Rc<RefCell>`),
328    /// but this is fine because it's created and used within a single task.
329    pub fn build_evm(&mut self) -> OverlayEvm<'_> {
330        let local = self.fresh_local();
331        self.build_evm_with_local(local)
332    }
333
334    /// Execute a non-committing call and return the raw [`ExecutionResult`].
335    ///
336    /// The EVM state is reverted to a checkpoint after execution on *both*
337    /// success and failure, so the call never mutates this overlay's dirty
338    /// layer. Each overlay simulation is therefore isolated: repeated calls all
339    /// observe the same base state.
340    ///
341    /// A revert or halt is *not* an error here — it is reported through the
342    /// returned [`ExecutionResult`] variant. Only failure to build or transact
343    /// the call yields `Err`.
344    ///
345    /// # Errors
346    ///
347    /// Returns an error if the [`TxEnv`] cannot be built from the given inputs,
348    /// or if revm fails to transact the call (for example a database error
349    /// while loading state from the RPC fallback).
350    ///
351    /// # Examples
352    ///
353    /// ```no_run
354    /// # use std::sync::Arc;
355    /// # use alloy_primitives::{Address, Bytes};
356    /// # use evm_fork_cache::cache::{EvmOverlay, EvmSnapshot};
357    /// # fn run(snapshot: Arc<EvmSnapshot>) -> Result<(), Box<dyn std::error::Error>> {
358    /// let mut overlay = EvmOverlay::new(snapshot, None);
359    /// let result = overlay.call_raw(Address::ZERO, Address::ZERO, Bytes::new())?;
360    /// // State is reverted; a second call sees the same base state.
361    /// let _again = overlay.call_raw(Address::ZERO, Address::ZERO, Bytes::new())?;
362    /// # let _ = result;
363    /// # Ok(())
364    /// # }
365    /// ```
366    pub fn call_raw(
367        &mut self,
368        from: Address,
369        to: Address,
370        calldata: Bytes,
371    ) -> Result<ExecutionResult> {
372        let tx = TxEnv::builder()
373            .caller(from)
374            .kind(TxKind::Call(to))
375            .data(calldata)
376            .value(U256::ZERO)
377            .build()
378            .map_err(OverlayError::tx_env)?;
379
380        // Recycle the reusable buffer (Pillar A.2): take it out as a plain Vec
381        // (keeping the overlay Send), lend it to a method-local Rc<RefCell> for
382        // revm's LocalContext, then reclaim and clear it after the EVM is dropped.
383        let buffer = Rc::new(RefCell::new(std::mem::take(&mut self.reusable_buffer)));
384        let local = LocalContext {
385            shared_memory_buffer: Rc::clone(&buffer),
386            precompile_error_message: None,
387        };
388
389        let result = {
390            let mut evm = self.build_evm_with_local(local);
391            use revm::context_interface::JournalTr;
392            let checkpoint = evm.journaled_state.checkpoint();
393            let result = evm.transact_one(tx).map_err(OverlayError::transact);
394            evm.journaled_state.checkpoint_revert(checkpoint);
395            result
396        };
397
398        self.reclaim_buffer(buffer);
399        result
400    }
401
402    /// Execute one non-committing call with temporary account-code overrides.
403    ///
404    /// Overrides live only for this call: the previous dirty-layer account (or
405    /// its absence) is restored before this method returns, including when the
406    /// EVM returns an execution error. The shared snapshot is never mutated.
407    /// This is useful for execution helpers whose control flow depends on an
408    /// external call but whose result does not depend on that callee's state;
409    /// for example, a revert-based V3 quoter transfers the output token before
410    /// deliberately reverting with the quote payload.
411    ///
412    /// Each supplied runtime bytecode replaces only the account's code and code
413    /// hash. Its balance, nonce, and account id continue to come from the
414    /// overlay/snapshot account when present.
415    ///
416    /// # Errors
417    ///
418    /// Returns an error if an overridden account cannot be loaded, the
419    /// transaction environment cannot be built, or revm cannot transact the
420    /// call. Empty bytecode is accepted and behaves like an EOA.
421    pub fn call_raw_with_code_overrides(
422        &mut self,
423        from: Address,
424        to: Address,
425        calldata: Bytes,
426        overrides: &[(Address, Bytes)],
427    ) -> Result<ExecutionResult> {
428        let mut prior = Vec::with_capacity(overrides.len());
429        for (address, runtime_bytecode) in overrides {
430            let old_dirty = self.dirty_accounts.get(address).cloned();
431            let mut info = match self.basic(*address) {
432                Ok(info) => info.unwrap_or_default(),
433                Err(error) => {
434                    for (prior_address, prior_info) in prior.into_iter().rev() {
435                        if let Some(info) = prior_info {
436                            self.dirty_accounts.insert(prior_address, info);
437                        } else {
438                            self.dirty_accounts.remove(&prior_address);
439                        }
440                    }
441                    return Err(OverlayError::transact(error));
442                }
443            };
444            let bytecode = Bytecode::new_raw(runtime_bytecode.clone());
445            info.code_hash = bytecode.hash_slow();
446            info.code = Some(bytecode);
447            self.dirty_accounts.insert(*address, info);
448            prior.push((*address, old_dirty));
449        }
450
451        let result = self.call_raw(from, to, calldata);
452
453        for (address, old_dirty) in prior.into_iter().rev() {
454            if let Some(info) = old_dirty {
455                self.dirty_accounts.insert(address, info);
456            } else {
457                self.dirty_accounts.remove(&address);
458            }
459        }
460        result
461    }
462
463    /// Reclaim the recycled shared-memory buffer after the EVM (and its
464    /// `LocalContext` clone of the `Rc`) has been dropped, clearing it for the
465    /// next call.
466    ///
467    /// The `Rc` was only ever held by the dropped EVM and this method's local, so
468    /// `try_unwrap` succeeds in the normal path. If a panic somewhere left an
469    /// extra strong reference the buffer is simply re-allocated next call — no
470    /// correctness impact.
471    fn reclaim_buffer(&mut self, buffer: Rc<RefCell<Vec<u8>>>) {
472        if let Ok(cell) = Rc::try_unwrap(buffer) {
473            let mut buf = cell.into_inner();
474            buf.clear();
475            self.reusable_buffer = buf;
476        } else {
477            self.reusable_buffer = Vec::with_capacity(self.buffer_capacity);
478        }
479    }
480
481    /// Build a revm EVM instance with an inspector, backed by this overlay, using
482    /// a caller-supplied [`LocalContext`].
483    ///
484    /// Like [`Self::build_evm_with_local`] but attaches `inspector`. The call
485    /// methods pass a `local` wrapping the recycled [`Self::reusable_buffer`]
486    /// (Pillar A.2) and reclaim it after the EVM is dropped.
487    fn build_evm_with_inspector_local<INSP>(
488        &mut self,
489        inspector: INSP,
490        local: LocalContext,
491    ) -> InspectorOverlayEvm<'_, INSP> {
492        let chain_id = self.snapshot.chain_id;
493        let spec_id = self.snapshot.spec_id;
494        let timestamp = self
495            .snapshot
496            .timestamp
497            .unwrap_or_else(|| unix_timestamp_secs_saturating(std::time::SystemTime::now()));
498        let block_number = self.snapshot.block_number;
499        let basefee = self.snapshot.basefee;
500        let coinbase = self.snapshot.coinbase;
501        let prevrandao = self.snapshot.prevrandao;
502        let gas_limit = self.snapshot.gas_limit;
503
504        let mut evm = Context::mainnet()
505            .with_db(&mut *self)
506            .with_local(local)
507            .modify_cfg_chained(|cfg| {
508                cfg.disable_nonce_check = true;
509                cfg.disable_eip3607 = true;
510                cfg.disable_base_fee = true;
511                cfg.disable_balance_check = true;
512                cfg.chain_id = chain_id;
513                cfg.limit_contract_code_size = None;
514                cfg.tx_chain_id_check = false;
515                cfg.spec = spec_id;
516            })
517            .build_mainnet_with_inspector(inspector);
518
519        evm.block.timestamp = U256::from(timestamp);
520        if let Some(number) = block_number {
521            evm.block.number = U256::from(number);
522        }
523        if let Some(basefee) = basefee {
524            evm.block.basefee = basefee;
525        }
526        if let Some(coinbase) = coinbase {
527            evm.block.beneficiary = coinbase;
528        }
529        if let Some(prevrandao) = prevrandao {
530            evm.block.prevrandao = Some(prevrandao);
531        }
532        if let Some(gas_limit) = gas_limit {
533            evm.block.gas_limit = gas_limit;
534        }
535        evm
536    }
537
538    /// Simulate a call with transfer tracking via the `TransferInspector`.
539    ///
540    /// This is the overlay-compatible equivalent of
541    /// [`super::EvmCache::simulate_with_transfer_tracking`]. It captures ERC20
542    /// Transfer events during execution to compute balance deltas for `owner`
543    /// (restricted to `tokens` when provided) without relying on pre/post
544    /// balance queries.
545    ///
546    /// On a reverting or halting call the EVM state is reverted to a checkpoint
547    /// before returning, so a failed simulation never mutates this overlay. On
548    /// success the call either commits the journaled changes into the overlay's
549    /// dirty layer (`commit == true`) or reverts them (`commit == false`); a
550    /// non-committing run leaves each overlay simulation isolated from the next.
551    ///
552    /// # Errors
553    ///
554    /// Returns an error if the [`TxEnv`] cannot be built, if revm fails to
555    /// transact the call, if the call reverts (mapped from the revert payload),
556    /// or if the call halts. In every error case the EVM state is reverted
557    /// first, regardless of `commit`.
558    ///
559    /// # Examples
560    ///
561    /// ```no_run
562    /// # use std::sync::Arc;
563    /// # use alloy_primitives::{Address, Bytes};
564    /// # use evm_fork_cache::cache::{EvmOverlay, EvmSnapshot};
565    /// # fn run(snapshot: Arc<EvmSnapshot>, token: Address, owner: Address) -> Result<(), Box<dyn std::error::Error>> {
566    /// let mut overlay = EvmOverlay::new(snapshot, None);
567    /// let sim = overlay.simulate_with_transfer_tracking(
568    ///     owner,
569    ///     token,
570    ///     Bytes::new(),
571    ///     owner,
572    ///     Some([token]),
573    ///     false, // non-committing: state is reverted afterwards
574    /// )?;
575    /// let _delta = sim.token_deltas.get(&token);
576    /// # Ok(())
577    /// # }
578    /// ```
579    pub fn simulate_with_transfer_tracking(
580        &mut self,
581        from: Address,
582        to: Address,
583        calldata: Bytes,
584        owner: Address,
585        tokens: Option<impl IntoIterator<Item = Address>>,
586        commit: bool,
587    ) -> SimulationResult<CallSimulationResult> {
588        let tx = TxEnv::builder()
589            .caller(from)
590            .kind(TxKind::Call(to))
591            .data(calldata)
592            .value(U256::ZERO)
593            .build()
594            .map_err(|e| SimError::Other(SimHostError::tx_env(e)))?;
595
596        let inspector = TransferInspector::new();
597
598        // Recycle the reusable buffer (Pillar A.2); reclaimed after the EVM drops.
599        let buffer = Rc::new(RefCell::new(std::mem::take(&mut self.reusable_buffer)));
600        let local = LocalContext {
601            shared_memory_buffer: Rc::clone(&buffer),
602            precompile_error_message: None,
603        };
604
605        let outcome = {
606            let mut evm = self.build_evm_with_inspector_local(inspector, local);
607
608            use revm::context_interface::JournalTr;
609            let checkpoint = evm.journaled_state.checkpoint();
610
611            let result = evm
612                .inspect_one_tx(tx)
613                .map_err(|e| SimError::Other(SimHostError::transact(e)));
614
615            match result {
616                Ok(ExecutionResult::Success {
617                    logs,
618                    gas_used,
619                    output,
620                    ..
621                }) => {
622                    let token_deltas = if let Some(token_list) = tokens {
623                        evm.inspector.balance_deltas_for_tokens(owner, token_list)
624                    } else {
625                        evm.inspector.balance_deltas(owner)
626                    };
627
628                    // Extract EIP-2930 access list from journaled state
629                    let access_list = extract_access_list(&evm.journaled_state.state);
630
631                    if commit {
632                        evm.commit_inner();
633                    } else {
634                        evm.journaled_state.checkpoint_revert(checkpoint);
635                    }
636
637                    Ok(CallSimulationResult {
638                        status: SimStatus::Success,
639                        gas_used,
640                        token_deltas,
641                        logs,
642                        access_list,
643                        output: output.into_data(),
644                    })
645                }
646                Ok(ExecutionResult::Revert { gas_used, output }) => {
647                    evm.journaled_state.checkpoint_revert(checkpoint);
648                    Err(SimulationError::from_revert(gas_used, output).into())
649                }
650                Ok(ExecutionResult::Halt { reason, gas_used }) => {
651                    evm.journaled_state.checkpoint_revert(checkpoint);
652                    Err(SimError::Halt {
653                        reason: format!("{reason:?}"),
654                        gas_used,
655                    })
656                }
657                Err(err) => {
658                    evm.journaled_state.checkpoint_revert(checkpoint);
659                    Err(err)
660                }
661            }
662        };
663
664        self.reclaim_buffer(buffer);
665        outcome
666    }
667
668    /// Run a single call with a caller-supplied [`Inspector`](revm::Inspector),
669    /// returning the raw [`ExecutionResult`] and handing the inspector back for the
670    /// caller to read.
671    ///
672    /// This is the inspector-generic public seam: where
673    /// [`Self::simulate_with_transfer_tracking`] hard-wires the
674    /// [`TransferInspector`], this accepts any
675    /// [`revm::Inspector`] — a [`CallTracer`](crate::tracing::CallTracer), an
676    /// [`InspectorStack`](crate::tracing::InspectorStack) composing several, or a
677    /// caller-defined one. It honors a full [`TxConfig`] (value/gas/nonce/access
678    /// list) exactly like [`Self::call_raw_with_access_list_with`] and recycles the
679    /// reusable shared-memory buffer like the other call methods.
680    ///
681    /// Unlike `simulate_with_transfer_tracking`, a revert or halt is **not** an
682    /// error: the raw [`ExecutionResult`] variant
683    /// ([`Success`](ExecutionResult::Success) /
684    /// [`Revert`](ExecutionResult::Revert) / [`Halt`](ExecutionResult::Halt)) is
685    /// returned as `Ok` so the inspector's captured frames (e.g. a reverted call
686    /// tree) remain observable. Only a tx-env build failure or a transact/database
687    /// error yields `Err`.
688    ///
689    /// On a successful transact the journaled changes are either committed into the
690    /// overlay's dirty layer (`commit == true`) or reverted (`commit == false`),
691    /// matching [`Self::simulate_with_transfer_tracking`]. On a revert/halt the
692    /// checkpoint is always reverted regardless of `commit`, so a failed call never
693    /// mutates this overlay. On a transact error the checkpoint is reverted too.
694    ///
695    /// # Errors
696    ///
697    /// Returns an error if the [`TxEnv`] cannot be built from `from`/`to`/`tx`, or
698    /// if revm fails to transact the call (e.g. a database error while loading
699    /// state).
700    ///
701    /// # Examples
702    ///
703    /// ```no_run
704    /// # use std::sync::Arc;
705    /// # use alloy_primitives::{Address, Bytes};
706    /// # use evm_fork_cache::cache::{EvmOverlay, EvmSnapshot, TxConfig};
707    /// # use evm_fork_cache::CallTracer;
708    /// # fn run(snapshot: Arc<EvmSnapshot>, to: Address) -> Result<(), Box<dyn std::error::Error>> {
709    /// let mut overlay = EvmOverlay::new(snapshot, None);
710    /// let (result, tracer) = overlay.call_raw_with_inspector(
711    ///     Address::ZERO,
712    ///     to,
713    ///     Bytes::new(),
714    ///     &TxConfig::default(),
715    ///     CallTracer::new(),
716    ///     false,
717    /// )?;
718    /// let _ = result;
719    /// let _trace = tracer.into_trace();
720    /// # Ok(())
721    /// # }
722    /// ```
723    pub fn call_raw_with_inspector<I>(
724        &mut self,
725        from: Address,
726        to: Address,
727        calldata: Bytes,
728        tx: &TxConfig,
729        inspector: I,
730        commit: bool,
731    ) -> SimulationResult<(ExecutionResult, I)>
732    where
733        I: for<'a> revm::Inspector<
734                Context<
735                    BlockEnv,
736                    TxEnv,
737                    CfgEnv,
738                    &'a mut EvmOverlay,
739                    Journal<&'a mut EvmOverlay>,
740                    (),
741                >,
742            >,
743    {
744        let mut builder = TxEnv::builder()
745            .caller(from)
746            .kind(TxKind::Call(to))
747            .data(calldata)
748            .value(tx.value);
749        if let Some(gas_limit) = tx.gas_limit {
750            builder = builder.gas_limit(gas_limit);
751        }
752        if let Some(gas_price) = tx.gas_price {
753            builder = builder.gas_price(gas_price);
754        }
755        if let Some(nonce) = tx.nonce {
756            builder = builder.nonce(nonce);
757        }
758        if let Some(access_list) = &tx.access_list {
759            builder = builder.access_list(access_list.clone());
760        }
761        let tx_env = builder
762            .build()
763            .map_err(|e| SimError::Other(SimHostError::tx_env(e)))?;
764
765        // Recycle the reusable buffer (Pillar A.2); reclaimed after the EVM drops.
766        let buffer = Rc::new(RefCell::new(std::mem::take(&mut self.reusable_buffer)));
767        let local = LocalContext {
768            shared_memory_buffer: Rc::clone(&buffer),
769            precompile_error_message: None,
770        };
771
772        let outcome = {
773            let mut evm = self.build_evm_with_inspector_local(inspector, local);
774
775            use revm::context_interface::JournalTr;
776            let checkpoint = evm.journaled_state.checkpoint();
777
778            match evm.inspect_one_tx(tx_env) {
779                Ok(result) => {
780                    if commit && matches!(result, ExecutionResult::Success { .. }) {
781                        evm.commit_inner();
782                    } else {
783                        evm.journaled_state.checkpoint_revert(checkpoint);
784                    }
785                    // Hand the inspector back to the caller.
786                    Ok((result, evm.inspector))
787                }
788                Err(e) => {
789                    evm.journaled_state.checkpoint_revert(checkpoint);
790                    Err(SimError::Other(SimHostError::transact(e)))
791                }
792            }
793        };
794
795        self.reclaim_buffer(buffer);
796        outcome
797    }
798
799    /// Apply `txs` in order against this overlay over **cumulative** block state,
800    /// with a revert policy and coinbase/miner-payment accounting (Phase 6
801    /// Track A+B).
802    ///
803    /// Each transaction observes the committed writes of the ones before it:
804    /// the bundle runs on a single overlay/EVM with one outer checkpoint plus a
805    /// per-transaction inner checkpoint, so it does **not** rebuild a fresh
806    /// overlay per transaction. See the [`bundle`](crate::bundle) module for the
807    /// public vocabulary ([`BundleTx`], [`BundleOptions`], [`RevertPolicy`],
808    /// [`TxOutcome`], [`BundleResult`]).
809    ///
810    /// # Revert policy
811    ///
812    /// - [`RevertPolicy::Atomic`]: the first transaction that reverts/halts
813    ///   rolls the whole bundle back to the outer checkpoint, sets
814    ///   `succeeded = false`, and stops (`per_tx` ends at the failing
815    ///   transaction). `coinbase_payment` is `0` and the overlay is unchanged.
816    /// - [`RevertPolicy::AllowReverts`]: a revert at a whitelisted index rolls
817    ///   back only that transaction (inner checkpoint) and execution continues;
818    ///   a revert at a non-whitelisted index behaves like `Atomic`.
819    ///
820    /// # Coinbase accounting
821    ///
822    /// `coinbase_payment` is the block beneficiary's balance delta across the kept
823    /// transactions. Under EIP-1559 revm credits the beneficiary only the priority
824    /// fee (`(effective_gas_price − basefee) × gas_used`) and burns the base fee
825    /// in-EVM, so the delta is the honest miner payment (plus any direct coinbase
826    /// tips). Saturating.
827    ///
828    /// # Commit semantics
829    ///
830    /// `opts.commit == true` folds the bundle's cumulative state into this
831    /// overlay's dirty layer (observable by subsequent overlay calls);
832    /// `false` reverts the outer checkpoint so the overlay is unchanged. A
833    /// failed atomic bundle never leaves partial state regardless of `commit`.
834    ///
835    /// # Errors
836    ///
837    /// Returns [`SimError`] if a transaction environment cannot be built or revm
838    /// fails to transact (e.g. a database error). A transaction *reverting* is
839    /// not an error — it is reported through the per-transaction
840    /// [`TxOutcome`] and the revert policy.
841    pub fn simulate_bundle(
842        &mut self,
843        txs: &[BundleTx],
844        opts: &BundleOptions,
845    ) -> SimulationResult<BundleResult> {
846        // Build every TxEnv up front so a build failure surfaces as an error
847        // before we touch the EVM/journal (and the borrow of `self` is clean).
848        let tx_envs: Vec<TxEnv> = txs
849            .iter()
850            .map(|bt| {
851                let mut builder = TxEnv::builder()
852                    .caller(bt.from)
853                    .kind(TxKind::Call(bt.to))
854                    .data(bt.calldata.clone())
855                    .value(bt.tx.value);
856                if let Some(gas_limit) = bt.tx.gas_limit {
857                    builder = builder.gas_limit(gas_limit);
858                }
859                if let Some(gas_price) = bt.tx.gas_price {
860                    builder = builder.gas_price(gas_price);
861                }
862                if let Some(nonce) = bt.tx.nonce {
863                    builder = builder.nonce(nonce);
864                }
865                if let Some(access_list) = &bt.tx.access_list {
866                    builder = builder.access_list(access_list.clone());
867                }
868                builder
869                    .build()
870                    .map_err(|e| SimError::Other(SimHostError::tx_env(e)))
871            })
872            .collect::<std::result::Result<_, _>>()?;
873
874        // Resolve the beneficiary and read its pre-bundle balance before the
875        // mutable borrow of `self` by the EVM (the post-bundle delta is the miner
876        // payment; revm already burns the base fee per EIP-1559).
877        let beneficiary = self
878            .snapshot
879            .coinbase
880            .unwrap_or_else(|| revm::context::BlockEnv::default().beneficiary);
881        let pre_beneficiary_balance = self
882            .basic(beneficiary)
883            .map_err(|e| SimError::Other(SimHostError::database(e)))?
884            .map(|info| info.balance)
885            .unwrap_or(U256::ZERO);
886
887        // Recycle the reusable buffer (Pillar A.2); reclaimed after the EVM drops.
888        let buffer = Rc::new(RefCell::new(std::mem::take(&mut self.reusable_buffer)));
889        let local = LocalContext {
890            shared_memory_buffer: Rc::clone(&buffer),
891            precompile_error_message: None,
892        };
893
894        let outcome = {
895            use revm::context_interface::JournalTr;
896            let mut evm = self.build_evm_with_local(local);
897
898            // Outer checkpoint: the whole-bundle savepoint.
899            let outer = evm.journaled_state.checkpoint();
900
901            let mut per_tx: Vec<TxOutcome> = Vec::with_capacity(tx_envs.len());
902            let mut total_gas: u64 = 0;
903            let mut aborted = false;
904
905            'bundle: for (idx, tx_env) in tx_envs.into_iter().enumerate() {
906                // Inner checkpoint: this transaction's savepoint.
907                let inner = evm.journaled_state.checkpoint();
908                let result = match evm.transact_one(tx_env) {
909                    Ok(result) => result,
910                    Err(e) => {
911                        // Host/transact error: undo this tx and the whole bundle,
912                        // reclaim the buffer, and surface as SimError.
913                        evm.journaled_state.checkpoint_revert(inner);
914                        evm.journaled_state.checkpoint_revert(outer);
915                        drop(evm);
916                        self.reclaim_buffer(buffer);
917                        return Err(SimError::Other(SimHostError::transact(e)));
918                    }
919                };
920
921                let gas_used = result.gas_used();
922                let reverted = !result.is_success();
923                let logs = result.logs().to_vec();
924                total_gas = total_gas.saturating_add(gas_used);
925
926                per_tx.push(TxOutcome {
927                    result,
928                    gas_used,
929                    reverted,
930                    logs,
931                });
932
933                if reverted {
934                    let allowed = match &opts.revert_policy {
935                        RevertPolicy::Atomic => false,
936                        RevertPolicy::AllowReverts(idxs) => idxs.contains(&idx),
937                    };
938                    if allowed {
939                        // Roll back only this transaction; later txs still run.
940                        evm.journaled_state.checkpoint_revert(inner);
941                        continue 'bundle;
942                    } else {
943                        // Atomic abort: roll the whole bundle back and stop.
944                        evm.journaled_state.checkpoint_revert(outer);
945                        aborted = true;
946                        break 'bundle;
947                    }
948                }
949                // Successful tx: its effects stay journaled for the next tx.
950            }
951
952            // Partition total gas into successful/reverted buckets in a single
953            // pass. Saturating (consistent with `total_gas`); the invariant
954            // `successful_tx_gas + reverted_tx_gas == total_gas` holds by
955            // construction since every executed tx lands in exactly one bucket.
956            let (successful_tx_gas, reverted_tx_gas) =
957                per_tx.iter().fold((0u64, 0u64), |(succ, rev), tx| {
958                    if tx.reverted {
959                        (succ, rev.saturating_add(tx.gas_used))
960                    } else {
961                        (succ.saturating_add(tx.gas_used), rev)
962                    }
963                });
964
965            if aborted {
966                // State is reverted to the pre-bundle outer checkpoint regardless
967                // of `commit`; no payment.
968                BundleResult {
969                    per_tx,
970                    coinbase_payment: U256::ZERO,
971                    gas_used: total_gas,
972                    successful_tx_gas,
973                    reverted_tx_gas,
974                    succeeded: false,
975                }
976            } else {
977                // Read the beneficiary's post-bundle balance from the journaled
978                // state (present iff it was touched) BEFORE commit/revert, since
979                // `commit_inner` finalizes (drains) the journal and an outer
980                // revert would undo the credit.
981                let post_beneficiary_balance = evm
982                    .journaled_state
983                    .state
984                    .get(&beneficiary)
985                    .map(|acct| acct.info.balance)
986                    .unwrap_or(pre_beneficiary_balance);
987                // revm already excludes the base fee from the beneficiary credit
988                // (EIP-1559), so the delta is the honest miner payment.
989                let coinbase_payment =
990                    post_beneficiary_balance.saturating_sub(pre_beneficiary_balance);
991
992                if opts.commit {
993                    evm.commit_inner();
994                } else {
995                    evm.journaled_state.checkpoint_revert(outer);
996                }
997
998                BundleResult {
999                    per_tx,
1000                    coinbase_payment,
1001                    gas_used: total_gas,
1002                    successful_tx_gas,
1003                    reverted_tx_gas,
1004                    succeeded: true,
1005                }
1006            }
1007        };
1008
1009        self.reclaim_buffer(buffer);
1010        Ok(outcome)
1011    }
1012
1013    /// Execute a non-committing call and return the result plus the touched
1014    /// [`StorageAccessList`].
1015    ///
1016    /// The access list is collected from every account marked touched in the
1017    /// journaled state after execution, recording both the touched accounts and
1018    /// the storage slots accessed under each.
1019    ///
1020    /// The EVM state is reverted to a checkpoint after a successful transact on
1021    /// both success and revert/halt outcomes, so the call never mutates this
1022    /// overlay's dirty layer and each overlay simulation stays isolated. As with
1023    /// [`Self::call_raw`], a revert or halt is reported through the returned
1024    /// [`ExecutionResult`] rather than as an error.
1025    ///
1026    /// # Errors
1027    ///
1028    /// Returns an error if the [`TxEnv`] cannot be built, or if revm fails to
1029    /// transact the call (for example a database error while loading state).
1030    ///
1031    /// # Examples
1032    ///
1033    /// ```no_run
1034    /// # use std::sync::Arc;
1035    /// # use alloy_primitives::{Address, Bytes};
1036    /// # use evm_fork_cache::cache::{EvmOverlay, EvmSnapshot};
1037    /// # fn run(snapshot: Arc<EvmSnapshot>) -> Result<(), Box<dyn std::error::Error>> {
1038    /// let mut overlay = EvmOverlay::new(snapshot, None);
1039    /// let (result, access_list) =
1040    ///     overlay.call_raw_with_access_list(Address::ZERO, Address::ZERO, Bytes::new())?;
1041    /// # let _ = (result, access_list);
1042    /// # Ok(())
1043    /// # }
1044    /// ```
1045    pub fn call_raw_with_access_list(
1046        &mut self,
1047        from: Address,
1048        to: Address,
1049        calldata: Bytes,
1050    ) -> Result<(ExecutionResult, StorageAccessList)> {
1051        self.call_raw_with_access_list_with(from, to, calldata, &TxConfig::default())
1052    }
1053
1054    /// Like [`call_raw_with_access_list`](Self::call_raw_with_access_list) but
1055    /// honors a full [`TxConfig`]: native `value`, `gas_limit`, `gas_price`,
1056    /// `nonce`, and a pre-warming EIP-2930 `access_list`.
1057    ///
1058    /// This is what the freshness optimistic loop uses so a [`SimRequest`]'s tx
1059    /// environment — e.g. a payable call carrying `value`, or a gas-bounded call
1060    /// — is reproduced faithfully instead of silently running as a zero-value,
1061    /// default-gas call. Like the shorthand it is non-committing (the checkpoint
1062    /// is reverted) and returns the captured storage access list.
1063    ///
1064    /// [`SimRequest`]: crate::freshness::SimRequest
1065    pub fn call_raw_with_access_list_with(
1066        &mut self,
1067        from: Address,
1068        to: Address,
1069        calldata: Bytes,
1070        tx: &TxConfig,
1071    ) -> Result<(ExecutionResult, StorageAccessList)> {
1072        let mut builder = TxEnv::builder()
1073            .caller(from)
1074            .kind(TxKind::Call(to))
1075            .data(calldata)
1076            .value(tx.value);
1077        if let Some(gas_limit) = tx.gas_limit {
1078            builder = builder.gas_limit(gas_limit);
1079        }
1080        if let Some(gas_price) = tx.gas_price {
1081            builder = builder.gas_price(gas_price);
1082        }
1083        if let Some(nonce) = tx.nonce {
1084            builder = builder.nonce(nonce);
1085        }
1086        if let Some(access_list) = &tx.access_list {
1087            builder = builder.access_list(access_list.clone());
1088        }
1089        let tx_env = builder.build().map_err(OverlayError::tx_env)?;
1090
1091        // Recycle the reusable buffer (Pillar A.2); reclaimed after the EVM drops.
1092        let buffer = Rc::new(RefCell::new(std::mem::take(&mut self.reusable_buffer)));
1093        let local = LocalContext {
1094            shared_memory_buffer: Rc::clone(&buffer),
1095            precompile_error_message: None,
1096        };
1097
1098        let outcome = {
1099            let mut evm = self.build_evm_with_local(local);
1100            use revm::context_interface::JournalTr;
1101            let checkpoint = evm.journaled_state.checkpoint();
1102            match evm.transact_one(tx_env) {
1103                Ok(result) => {
1104                    let mut access_list = StorageAccessList::default();
1105                    for (address, account) in evm.journaled_state.state.iter() {
1106                        if account.is_touched() {
1107                            access_list.accounts.insert(*address);
1108                            let code_hash = account.info.code_hash;
1109                            if code_hash != B256::ZERO
1110                                && code_hash != revm::primitives::KECCAK_EMPTY
1111                            {
1112                                access_list.code_hashes.insert(code_hash);
1113                            }
1114                            for slot_key in account.storage.keys() {
1115                                access_list.slots.insert((*address, *slot_key));
1116                            }
1117                        }
1118                    }
1119                    evm.journaled_state.checkpoint_revert(checkpoint);
1120                    Ok((result, access_list))
1121                }
1122                Err(e) => {
1123                    // Revert the checkpoint even on a host/transact error so the EVM
1124                    // journal is not left dirty (mirrors `call_raw`).
1125                    evm.journaled_state.checkpoint_revert(checkpoint);
1126                    Err(OverlayError::transact(e))
1127                }
1128            }
1129        };
1130
1131        self.reclaim_buffer(buffer);
1132        outcome
1133    }
1134
1135    /// Execute a non-committing call with cooperative cancellation.
1136    ///
1137    /// This has the same transaction and access-list semantics as
1138    /// [`Self::call_raw_with_access_list_with`]. The supplied cancellation scope
1139    /// may be shared by related overlay calls and is checked at every EVM
1140    /// instruction boundary. If cancellation is observed, execution halts, the
1141    /// journal checkpoint is reverted, and [`OverlayError::Cancelled`] is
1142    /// returned. An uncancelled execution returns the same result and access-list
1143    /// evidence as the existing API.
1144    ///
1145    /// Cancellation cannot interrupt a database callback or precompile that is
1146    /// already executing; it is observed at the next EVM instruction boundary.
1147    /// For a wholly provider-free cancellation path, construct this overlay with
1148    /// no external database. A scope cannot be reset after cancellation and must
1149    /// not be reused for a later independent candidate.
1150    ///
1151    /// # Errors
1152    ///
1153    /// Returns [`OverlayError::Cancelled`] when the token is cancelled, or the
1154    /// same transaction-environment and host errors as
1155    /// [`Self::call_raw_with_access_list_with`].
1156    pub fn call_raw_with_access_list_with_cancellation(
1157        &mut self,
1158        from: Address,
1159        to: Address,
1160        calldata: Bytes,
1161        tx: &TxConfig,
1162        cancellation: &SimulationCancellationToken,
1163    ) -> Result<(ExecutionResult, StorageAccessList)> {
1164        if cancellation.is_cancelled() {
1165            return Err(OverlayError::Cancelled);
1166        }
1167
1168        let mut builder = TxEnv::builder()
1169            .caller(from)
1170            .kind(TxKind::Call(to))
1171            .data(calldata)
1172            .value(tx.value);
1173        if let Some(gas_limit) = tx.gas_limit {
1174            builder = builder.gas_limit(gas_limit);
1175        }
1176        if let Some(gas_price) = tx.gas_price {
1177            builder = builder.gas_price(gas_price);
1178        }
1179        if let Some(nonce) = tx.nonce {
1180            builder = builder.nonce(nonce);
1181        }
1182        if let Some(access_list) = &tx.access_list {
1183            builder = builder.access_list(access_list.clone());
1184        }
1185        let tx_env = builder.build().map_err(OverlayError::tx_env)?;
1186
1187        let buffer = Rc::new(RefCell::new(std::mem::take(&mut self.reusable_buffer)));
1188        let local = LocalContext {
1189            shared_memory_buffer: Rc::clone(&buffer),
1190            precompile_error_message: None,
1191        };
1192        let inspector = SimulationCancellationInspector {
1193            token: cancellation.clone(),
1194        };
1195
1196        let outcome = {
1197            let mut evm = self.build_evm_with_inspector_local(inspector, local);
1198            use revm::context_interface::JournalTr;
1199            let checkpoint = evm.journaled_state.checkpoint();
1200            match evm.inspect_one_tx(tx_env) {
1201                Ok(result) => {
1202                    let cancelled = cancellation.is_cancelled();
1203                    let mut access_list = StorageAccessList::default();
1204                    if !cancelled {
1205                        for (address, account) in evm.journaled_state.state.iter() {
1206                            if account.is_touched() {
1207                                access_list.accounts.insert(*address);
1208                                let code_hash = account.info.code_hash;
1209                                if code_hash != B256::ZERO
1210                                    && code_hash != revm::primitives::KECCAK_EMPTY
1211                                {
1212                                    access_list.code_hashes.insert(code_hash);
1213                                }
1214                                for slot_key in account.storage.keys() {
1215                                    access_list.slots.insert((*address, *slot_key));
1216                                }
1217                            }
1218                        }
1219                    }
1220                    evm.journaled_state.checkpoint_revert(checkpoint);
1221                    if cancelled {
1222                        Err(OverlayError::Cancelled)
1223                    } else {
1224                        Ok((result, access_list))
1225                    }
1226                }
1227                Err(error) => {
1228                    evm.journaled_state.checkpoint_revert(checkpoint);
1229                    if cancellation.is_cancelled() {
1230                        Err(OverlayError::Cancelled)
1231                    } else {
1232                        Err(OverlayError::transact(error))
1233                    }
1234                }
1235            }
1236        };
1237
1238        self.reclaim_buffer(buffer);
1239        outcome
1240    }
1241
1242    /// Write a storage value into this overlay's dirty layer.
1243    ///
1244    /// The dirty layer takes precedence over the snapshot on subsequent reads
1245    /// (see the lookup order on [`EvmOverlay`]), so this injects a value into a
1246    /// snapshot-backed overlay without mutating the shared snapshot.
1247    ///
1248    /// # Freshness validation
1249    ///
1250    /// This is the freshness validator's correction step. When a slot the
1251    /// snapshot captured is found to be stale, the validator writes the
1252    /// freshly-fetched value here and then re-runs the simulation (e.g. via
1253    /// [`Self::call_raw`]): the re-run reads the corrected slot out of the dirty
1254    /// layer instead of the stale snapshot value, so the corrected result
1255    /// becomes observable. Because the override lives only in this overlay,
1256    /// other overlays sharing the same `Arc<EvmSnapshot>` are unaffected.
1257    ///
1258    /// # Examples
1259    ///
1260    /// ```no_run
1261    /// # use std::sync::Arc;
1262    /// # use alloy_primitives::{Address, Bytes, U256};
1263    /// # use evm_fork_cache::cache::{EvmOverlay, EvmSnapshot};
1264    /// # fn run(snapshot: Arc<EvmSnapshot>, token: Address, slot: U256) -> Result<(), Box<dyn std::error::Error>> {
1265    /// let mut overlay = EvmOverlay::new(snapshot, None);
1266    /// // Inject the fresh value, then re-run to observe the corrected result.
1267    /// overlay.override_slot(token, slot, U256::from(42u64));
1268    /// let corrected = overlay.call_raw(Address::ZERO, token, Bytes::new())?;
1269    /// # let _ = corrected;
1270    /// # Ok(())
1271    /// # }
1272    /// ```
1273    pub fn override_slot(&mut self, address: Address, slot: U256, value: U256) {
1274        self.dirty_storage
1275            .entry(address)
1276            .or_default()
1277            .insert(slot, value);
1278    }
1279
1280    /// Execute a non-committing typed Solidity call from [`Address::ZERO`],
1281    /// decoding the return — the overlay counterpart to
1282    /// [`EvmCache::call_sol`](super::EvmCache::call_sol).
1283    ///
1284    /// ```no_run
1285    /// # use std::sync::Arc;
1286    /// # use alloy_primitives::Address;
1287    /// # use alloy_sol_types::sol;
1288    /// # use evm_fork_cache::cache::{EvmOverlay, EvmSnapshot};
1289    /// # sol! { interface IErc20 { function balanceOf(address account) returns (uint256); } }
1290    /// # fn run(mut overlay: EvmOverlay, token: Address, alice: Address) -> Result<(), Box<dyn std::error::Error>> {
1291    /// let bal = overlay.call_sol(token, IErc20::balanceOfCall { account: alice })?;
1292    /// # let _ = bal; Ok(()) }
1293    /// ```
1294    pub fn call_sol<C: SolCall>(&mut self, to: Address, call: C) -> Result<C::Return> {
1295        self.call_sol_from(Address::ZERO, to, call)
1296    }
1297
1298    /// Execute a non-committing typed Solidity call from an explicit sender,
1299    /// decoding the return.
1300    pub fn call_sol_from<C: SolCall>(
1301        &mut self,
1302        from: Address,
1303        to: Address,
1304        call: C,
1305    ) -> Result<C::Return> {
1306        let result = self.call_raw(from, to, Bytes::from(call.abi_encode()))?;
1307        match result {
1308            ExecutionResult::Success { output, .. } => {
1309                let output = output.into_data();
1310                C::abi_decode_returns(&output).map_err(|error| OverlayError::SolCallDecode {
1311                    signature: C::SIGNATURE,
1312                    from,
1313                    to,
1314                    output_len: output.len(),
1315                    details: format!("{error:?}"),
1316                })
1317            }
1318            other => Err(OverlayError::SolCallFailed {
1319                signature: C::SIGNATURE,
1320                from,
1321                to,
1322                result: format!("{other:?}"),
1323            }),
1324        }
1325    }
1326
1327    /// Mock `holder`'s ERC-20 balance of `token` to `amount` — **overlay-local**.
1328    ///
1329    /// Discovers the balance mapping slot and layout (Solidity / Vyper / Solady)
1330    /// from a single `balanceOf(holder)` simulation, writes `amount` to that slot
1331    /// in this overlay's dirty layer via [`override_slot`](Self::override_slot),
1332    /// and verifies. The cache and snapshot are never mutated; the mock is
1333    /// dropped with the overlay.
1334    ///
1335    /// Returns `Ok(true)` if set and verified, `Ok(false)` if no balance slot was
1336    /// discoverable or the write did not drive the return (e.g. a rebasing token,
1337    /// or `holder == Address::ZERO`, which is refused). A failed attempt leaves no
1338    /// stray write.
1339    pub fn mock_balance(
1340        &mut self,
1341        token: Address,
1342        holder: Address,
1343        amount: U256,
1344    ) -> SimulationResult<bool> {
1345        if holder == Address::ZERO {
1346            return Ok(false);
1347        }
1348        let calldata = Bytes::from(IERC20::balanceOfCall { target: holder }.abi_encode());
1349        let holder_word = holder.into_word();
1350        self.mock_slot_driving(token, calldata, amount, move |probe, ret| {
1351            probe
1352                .accesses(&[holder_word])
1353                .into_iter()
1354                .filter(|a| a.keyed_by(holder_word))
1355                .max_by_key(|a| (a.value == ret, a.confidence))
1356                .map(|a| (a.slot, a.value))
1357        })
1358    }
1359
1360    /// Mock `owner`'s ERC-20 allowance to `spender` on `token` — overlay-local.
1361    ///
1362    /// Discovers the (nested) `allowance` mapping entry keyed by both addresses,
1363    /// writes `amount` (pass `U256::MAX` for "unlimited"), and verifies. Refuses
1364    /// `owner == Address::ZERO`. Same isolation and failure semantics as
1365    /// [`mock_balance`](Self::mock_balance).
1366    pub fn mock_allowance(
1367        &mut self,
1368        token: Address,
1369        owner: Address,
1370        spender: Address,
1371        amount: U256,
1372    ) -> SimulationResult<bool> {
1373        if owner == Address::ZERO {
1374            return Ok(false);
1375        }
1376        let calldata = Bytes::from(IERC20::allowanceCall { owner, spender }.abi_encode());
1377        let (owner_word, spender_word) = (owner.into_word(), spender.into_word());
1378        self.mock_slot_driving(token, calldata, amount, move |probe, _ret| {
1379            probe
1380                .accesses(&[owner_word, spender_word])
1381                .into_iter()
1382                .filter(|a| a.keyed_by(owner_word) && a.keyed_by(spender_word))
1383                .max_by_key(|a| (a.depth, a.confidence))
1384                .map(|a| (a.slot, a.value))
1385        })
1386    }
1387
1388    /// Mock the return value of a single-word view call by finding the storage
1389    /// slot that drives it and overriding that slot — overlay-local.
1390    ///
1391    /// Runs `to.calldata`, identifies the `SLOAD` whose loaded value equals the
1392    /// call's returned word (see
1393    /// [`HashStorageProbe::slots_returning`](crate::mapping_probe::HashStorageProbe::slots_returning)),
1394    /// writes `desired` there, and verifies the call now returns `desired`. Works
1395    /// for balances, allowances, `totalSupply`, and any getter that returns a
1396    /// single stored word. Returns `Ok(false)` (leaving no stray write) when the
1397    /// return is computed from more than one slot, so it can't be set by a single
1398    /// override.
1399    pub fn mock_view(
1400        &mut self,
1401        to: Address,
1402        calldata: Bytes,
1403        desired: U256,
1404    ) -> SimulationResult<bool> {
1405        self.mock_slot_driving(to, calldata, desired, |probe, ret| {
1406            probe
1407                .slots_returning(ret)
1408                .into_iter()
1409                .next()
1410                .map(|slot| (slot, ret))
1411        })
1412    }
1413
1414    /// Typed [`mock_view`](Self::mock_view): mock the `desired` return of a
1415    /// [`SolCall`] getter that returns a single word.
1416    ///
1417    /// ```no_run
1418    /// # use std::sync::Arc;
1419    /// # use alloy_primitives::{Address, U256};
1420    /// # use alloy_sol_types::sol;
1421    /// # use evm_fork_cache::cache::{EvmOverlay, EvmSnapshot};
1422    /// # sol! { interface IErc20 { function totalSupply() returns (uint256); } }
1423    /// # fn run(mut overlay: EvmOverlay, token: Address) -> Result<(), Box<dyn std::error::Error>> {
1424    /// overlay.mock_call(token, IErc20::totalSupplyCall {}, U256::from(1_000u64))?;
1425    /// # Ok(()) }
1426    /// ```
1427    pub fn mock_call<C: SolCall>(
1428        &mut self,
1429        to: Address,
1430        call: C,
1431        desired: U256,
1432    ) -> SimulationResult<bool> {
1433        self.mock_view(to, Bytes::from(call.abi_encode()), desired)
1434    }
1435
1436    /// Extract the leading 32-byte word of a successful call's return data.
1437    fn success_word(result: &ExecutionResult) -> Option<U256> {
1438        match result {
1439            ExecutionResult::Success { output, .. } => {
1440                let data = output.data();
1441                (data.len() >= 32).then(|| U256::from_be_slice(&data[..32]))
1442            }
1443            _ => None,
1444        }
1445    }
1446
1447    /// Shared core for the `mock_*` methods: discover the slot driving
1448    /// `to.calldata`'s return via `choose`, override it to `desired`, verify, and
1449    /// restore the slot on a failed verify so a mis-pick leaves no stray write.
1450    fn mock_slot_driving<F>(
1451        &mut self,
1452        to: Address,
1453        calldata: Bytes,
1454        desired: U256,
1455        choose: F,
1456    ) -> SimulationResult<bool>
1457    where
1458        F: FnOnce(&HashStorageProbe, U256) -> Option<(B256, U256)>,
1459    {
1460        let (result, probe) = self.call_raw_with_inspector(
1461            Address::ZERO,
1462            to,
1463            calldata.clone(),
1464            &TxConfig::default(),
1465            HashStorageProbe::new(),
1466            false,
1467        )?;
1468        let Some(ret) = Self::success_word(&result) else {
1469            return Ok(false);
1470        };
1471        let Some((slot, prev)) = choose(&probe, ret) else {
1472            return Ok(false);
1473        };
1474        let slot_u = U256::from_be_bytes(slot.0);
1475        self.override_slot(to, slot_u, desired);
1476
1477        let (verify, _) = self.call_raw_with_inspector(
1478            Address::ZERO,
1479            to,
1480            calldata,
1481            &TxConfig::default(),
1482            HashStorageProbe::new(),
1483            false,
1484        )?;
1485        if Self::success_word(&verify) == Some(desired) {
1486            Ok(true)
1487        } else {
1488            self.override_slot(to, slot_u, prev); // undo the mis-pick
1489            Ok(false)
1490        }
1491    }
1492}
1493
1494impl revm::database_interface::DatabaseCommit for EvmOverlay {
1495    fn commit(&mut self, changes: alloy_primitives::map::HashMap<Address, revm::state::Account>) {
1496        for (address, account) in changes {
1497            self.dirty_accounts.insert(address, account.info);
1498            let storage = self.dirty_storage.entry(address).or_default();
1499            for (slot, value) in account.storage {
1500                storage.insert(slot, value.present_value);
1501            }
1502        }
1503    }
1504}
1505
1506impl Database for EvmOverlay {
1507    type Error = DatabaseError;
1508
1509    fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
1510        // 1. Check dirty layer
1511        if let Some(info) = self.dirty_accounts.get(&address) {
1512            return Ok(Some(info.clone()));
1513        }
1514        // 2. Check snapshot (O(1) HashMap lookup, no locks). `account_info` folds
1515        //    the two snapshot tiers (overlay ▸ base) and already short-circuits a
1516        //    NotExisting account to None — it must NOT fall through to the ext_db,
1517        //    mirroring revm `DbAccount::info()` and the live `EvmCache` read.
1518        if self.snapshot.accounts_not_existing.contains(&address) {
1519            return Ok(None);
1520        }
1521        if let Some(info) = self.snapshot.account_info(address) {
1522            return Ok(Some(info.clone()));
1523        }
1524        // 3. RPC fallback
1525        if let Some(ref ext_db) = self.ext_db {
1526            let info = ext_db.basic_ref(address)?;
1527            if let Some(ref info) = info {
1528                self.dirty_accounts.insert(address, info.clone());
1529            }
1530            return Ok(info);
1531        }
1532        self.missing_state.accounts.insert(address);
1533        Ok(None)
1534    }
1535
1536    fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
1537        // Check dirty accounts first
1538        for info in self.dirty_accounts.values() {
1539            if info.code_hash == code_hash
1540                && let Some(code) = &info.code
1541            {
1542                return Ok(code.clone());
1543            }
1544        }
1545        // Check the snapshot's code index (overlay ▸ base).
1546        if let Some(code) = self.snapshot.code(code_hash) {
1547            return Ok(code.clone());
1548        }
1549        // RPC fallback
1550        if let Some(ref ext_db) = self.ext_db {
1551            return ext_db.code_by_hash_ref(code_hash);
1552        }
1553        self.missing_state.code_hashes.insert(code_hash);
1554        Ok(Bytecode::default())
1555    }
1556
1557    fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
1558        // 1. Check dirty layer
1559        if let Some(account_storage) = self.dirty_storage.get(&address)
1560            && let Some(value) = account_storage.get(&index)
1561        {
1562            return Ok(*value);
1563        }
1564        // 2. Check snapshot (O(1)). `storage_value` folds the two tiers (overlay ▸
1565        //    cleared-as-ZERO ▸ base); a cleared account's absent slot reads ZERO
1566        //    and must NOT fall through to the ext_db, mirroring the live EVM SLOAD
1567        //    for a StorageCleared/NotExisting account.
1568        if let Some(value) = self.snapshot.storage_value(address, index) {
1569            return Ok(value);
1570        }
1571        // 3. RPC fallback
1572        if let Some(ref ext_db) = self.ext_db {
1573            let value = ext_db.storage_ref(address, index)?;
1574            self.dirty_storage
1575                .entry(address)
1576                .or_default()
1577                .insert(index, value);
1578            return Ok(value);
1579        }
1580        self.missing_state.storage.insert((address, index));
1581        Ok(U256::ZERO)
1582    }
1583
1584    fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
1585        if let Some(hash) = self.snapshot.block_hashes.get(&number) {
1586            return Ok(*hash);
1587        }
1588        if let Some(ref ext_db) = self.ext_db {
1589            return ext_db.block_hash_ref(number);
1590        }
1591        // A hash that was not resident when the snapshot was taken cannot be
1592        // fetched by an RPC-disconnected overlay, so `BLOCKHASH` resolves to
1593        // ZERO. The fallback is recorded so readiness validation fails closed
1594        // instead of confirming control flow that may depend on the real hash.
1595        self.blockhash_zero_fallback = true;
1596        self.missing_state.block_hashes.insert(number);
1597        Ok(B256::ZERO)
1598    }
1599}
1600
1601fn extract_access_list(state: &revm::state::EvmState) -> AccessList {
1602    let items: Vec<AccessListItem> = state
1603        .iter()
1604        .filter(|(_, account)| account.is_touched())
1605        .map(|(address, account)| AccessListItem {
1606            address: *address,
1607            storage_keys: account
1608                .storage
1609                .keys()
1610                .map(|slot| B256::from(*slot))
1611                .collect(),
1612        })
1613        .collect();
1614    AccessList(items)
1615}
1616
1617#[cfg(test)]
1618mod tests {
1619    use super::*;
1620    use crate::cache::snapshot::BaseState;
1621    use revm::primitives::hardfork::SpecId;
1622    use std::collections::HashSet;
1623
1624    /// Build a two-tier `EvmSnapshot` whose cold base holds the given accounts,
1625    /// storage, and code, with an empty hot overlay — the shape
1626    /// `snapshot_deep_clone` produces. The `Arc`-per-account storage of the
1627    /// base is built from the plain per-account maps.
1628    fn snap(
1629        accounts: HashMap<Address, AccountInfo>,
1630        storage: HashMap<Address, HashMap<U256, U256>>,
1631        code_by_hash: HashMap<B256, Bytecode>,
1632        block_hashes: HashMap<u64, B256>,
1633    ) -> Arc<EvmSnapshot> {
1634        let base = BaseState {
1635            accounts,
1636            storage: storage
1637                .into_iter()
1638                .map(|(addr, slots)| (addr, Arc::new(slots)))
1639                .collect(),
1640            code_by_hash,
1641        };
1642        Arc::new(EvmSnapshot {
1643            base: Arc::new(base),
1644            overlay_accounts: HashMap::new(),
1645            overlay_storage: HashMap::new(),
1646            overlay_code_by_hash: HashMap::new(),
1647            storage_cleared: HashSet::new(),
1648            accounts_not_existing: HashSet::new(),
1649            block_hashes,
1650            block_context_hash: None,
1651            block_number: None,
1652            basefee: None,
1653            coinbase: None,
1654            prevrandao: None,
1655            gas_limit: None,
1656            chain_id: 42161,
1657            timestamp: None,
1658            spec_id: SpecId::CANCUN,
1659            shared_memory_capacity: 64_000,
1660        })
1661    }
1662
1663    #[test]
1664    fn test_overlay_is_send() {
1665        fn assert_send<T: Send>() {}
1666        assert_send::<EvmOverlay>();
1667    }
1668
1669    #[test]
1670    fn blockhash_zero_fallback_flags_only_unresolved_reads() {
1671        let known = B256::repeat_byte(0xAB);
1672        let snapshot = snap(
1673            HashMap::new(),
1674            HashMap::new(),
1675            HashMap::new(),
1676            HashMap::from([(5u64, known)]),
1677        );
1678        let mut overlay = EvmOverlay::new(snapshot, None);
1679
1680        // A snapshot-provided hash resolves for real: no flag.
1681        assert_eq!(overlay.block_hash(5).unwrap(), known);
1682        assert!(!overlay.blockhash_zero_fallback());
1683
1684        // An untracked number falls back to ZERO and is flagged so the
1685        // freshness validator can fail closed.
1686        assert_eq!(overlay.block_hash(6).unwrap(), B256::ZERO);
1687        assert!(overlay.blockhash_zero_fallback());
1688
1689        // The flag is per-simulation state: reset clears it.
1690        overlay.reset();
1691        assert!(!overlay.blockhash_zero_fallback());
1692    }
1693
1694    #[test]
1695    fn test_overlay_basic_from_snapshot() {
1696        let mut accounts = HashMap::new();
1697        let info = AccountInfo {
1698            balance: U256::from(1000),
1699            nonce: 1,
1700            code_hash: B256::ZERO,
1701            code: None,
1702            account_id: None,
1703        };
1704        let addr = Address::repeat_byte(0x01);
1705        accounts.insert(addr, info);
1706
1707        let snapshot = snap(accounts, HashMap::new(), HashMap::new(), HashMap::new());
1708
1709        let mut overlay = EvmOverlay::new(snapshot, None);
1710        let result = overlay.basic(addr).unwrap();
1711        assert!(result.is_some());
1712        assert_eq!(result.unwrap().balance, U256::from(1000));
1713    }
1714
1715    #[test]
1716    fn test_overlay_storage_from_snapshot() {
1717        let addr = Address::repeat_byte(0x01);
1718        let slot = U256::from(42);
1719        let value = U256::from(999);
1720
1721        let mut storage = HashMap::new();
1722        let mut account_storage = HashMap::new();
1723        account_storage.insert(slot, value);
1724        storage.insert(addr, account_storage);
1725
1726        let snapshot = snap(HashMap::new(), storage, HashMap::new(), HashMap::new());
1727
1728        let mut overlay = EvmOverlay::new(snapshot, None);
1729        let result = overlay.storage(addr, slot).unwrap();
1730        assert_eq!(result, value);
1731    }
1732
1733    #[test]
1734    fn test_overlay_dirty_overrides_snapshot() {
1735        let addr = Address::repeat_byte(0x01);
1736        let slot = U256::from(42);
1737
1738        let mut storage = HashMap::new();
1739        let mut account_storage = HashMap::new();
1740        account_storage.insert(slot, U256::from(100));
1741        storage.insert(addr, account_storage);
1742
1743        let snapshot = snap(HashMap::new(), storage, HashMap::new(), HashMap::new());
1744
1745        let mut overlay = EvmOverlay::new(snapshot, None);
1746
1747        // Write to dirty layer
1748        overlay
1749            .dirty_storage
1750            .entry(addr)
1751            .or_default()
1752            .insert(slot, U256::from(200));
1753
1754        // Should read dirty value, not snapshot
1755        let result = overlay.storage(addr, slot).unwrap();
1756        assert_eq!(result, U256::from(200));
1757    }
1758
1759    #[test]
1760    fn test_overlay_missing_returns_zero() {
1761        let snapshot = snap(
1762            HashMap::new(),
1763            HashMap::new(),
1764            HashMap::new(),
1765            HashMap::new(),
1766        );
1767
1768        let mut overlay = EvmOverlay::new(snapshot, None);
1769        let addr = Address::repeat_byte(0x99);
1770        let result = overlay.storage(addr, U256::from(1)).unwrap();
1771        assert_eq!(result, U256::ZERO);
1772
1773        let account = overlay.basic(addr).unwrap();
1774        assert!(account.is_none());
1775    }
1776
1777    #[test]
1778    fn test_overlay_code_by_hash_from_snapshot() {
1779        let code = Bytecode::new_raw(Bytes::from(vec![0x60, 0x00, 0x60, 0x00]));
1780        let hash = code.hash_slow();
1781
1782        let mut code_by_hash = HashMap::new();
1783        code_by_hash.insert(hash, code.clone());
1784
1785        let snapshot = snap(HashMap::new(), HashMap::new(), code_by_hash, HashMap::new());
1786
1787        let mut overlay = EvmOverlay::new(snapshot, None);
1788        let result = overlay.code_by_hash(hash).unwrap();
1789        assert_eq!(result.len(), 4);
1790    }
1791
1792    #[test]
1793    fn test_overlay_block_hash() {
1794        let mut block_hashes = HashMap::new();
1795        let hash = B256::repeat_byte(0xAB);
1796        block_hashes.insert(42u64, hash);
1797
1798        let snapshot = snap(HashMap::new(), HashMap::new(), HashMap::new(), block_hashes);
1799
1800        let mut overlay = EvmOverlay::new(snapshot, None);
1801        assert_eq!(overlay.block_hash(42).unwrap(), hash);
1802        assert_eq!(overlay.block_hash(99).unwrap(), B256::ZERO);
1803    }
1804}