Skip to main content

revmc_statetest/
diagnostic.rs

1// Diagnostic utilities for comparing interpreter vs JIT execution.
2
3use crate::{
4    compiled::{CompileCache, CompiledContracts},
5    merkle_trie::compute_test_roots,
6};
7use revm::{
8    context::{block::BlockEnv, cfg::CfgEnv, tx::TxEnv},
9    context_interface::result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction},
10    database::{self, bal::EvmDatabaseError},
11    database_interface::{DatabaseCommit, EmptyDB},
12    handler::Handler,
13    inspector::{inspectors::TracerEip3155, InspectCommitEvm},
14    primitives::{hardfork::SpecId, Bytes, B256},
15    Context, ExecuteCommitEvm, MainBuilder, MainContext,
16};
17use std::{convert::Infallible, io::stderr};
18
19type ExecResult =
20    Result<ExecutionResult<HaltReason>, EVMError<EvmDatabaseError<Infallible>, InvalidTransaction>>;
21
22/// Snapshot of a single test execution for comparison.
23#[derive(Debug)]
24pub struct ExecutionSnapshot {
25    pub status: ExecStatus,
26    pub output: Option<Bytes>,
27    pub gas_used: u64,
28    pub state_root: B256,
29    pub logs_root: B256,
30    pub post_state_dump: String,
31}
32
33/// Summarized execution outcome.
34#[derive(Debug)]
35pub enum ExecStatus {
36    Success(String),
37    Revert,
38    Halt(String),
39    Error(String),
40}
41
42impl std::fmt::Display for ExecStatus {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        match self {
45            Self::Success(r) => write!(f, "Success({r})"),
46            Self::Revert => write!(f, "Revert"),
47            Self::Halt(r) => write!(f, "Halt({r})"),
48            Self::Error(e) => write!(f, "Error({e})"),
49        }
50    }
51}
52
53fn snapshot_from_result(
54    exec_result: &ExecResult,
55    db: &database::State<EmptyDB>,
56) -> ExecutionSnapshot {
57    let validation = compute_test_roots(exec_result, db);
58
59    let (status, output, gas_used) = match exec_result {
60        Ok(result) => {
61            let status = match result {
62                ExecutionResult::Success { reason, .. } => {
63                    ExecStatus::Success(format!("{reason:?}"))
64                }
65                ExecutionResult::Revert { .. } => ExecStatus::Revert,
66                ExecutionResult::Halt { reason, .. } => ExecStatus::Halt(format!("{reason:?}")),
67            };
68            (status, result.output().cloned(), result.gas_used())
69        }
70        Err(e) => (ExecStatus::Error(e.to_string()), None, 0),
71    };
72
73    ExecutionSnapshot {
74        status,
75        output,
76        gas_used,
77        state_root: validation.state_root,
78        logs_root: validation.logs_root,
79        post_state_dump: db.cache.pretty_print(),
80    }
81}
82
83/// Run a single test case with the interpreter, returning an execution snapshot.
84pub fn run_interpreter(
85    cfg: &CfgEnv,
86    block: &BlockEnv,
87    tx: &TxEnv,
88    cache_state: &database::CacheState,
89) -> ExecutionSnapshot {
90    let prestate = cache_state.clone();
91    let mut state =
92        database::State::builder().with_cached_prestate(prestate).with_bundle_update().build();
93
94    let mut evm = Context::mainnet()
95        .with_block(block)
96        .with_tx(tx)
97        .with_cfg(cfg.clone())
98        .with_db(&mut state)
99        .build_mainnet();
100    let exec_result = evm.transact_commit(tx);
101    let db = evm.ctx.journaled_state.database;
102
103    snapshot_from_result(&exec_result, db)
104}
105
106/// Run a single test case with JIT-compiled functions, returning an execution snapshot.
107pub fn run_jit(
108    compiled: &CompiledContracts,
109    cache: &CompileCache,
110    spec_id: SpecId,
111    cfg: &CfgEnv,
112    block: &BlockEnv,
113    tx: &TxEnv,
114    cache_state: &database::CacheState,
115) -> ExecutionSnapshot {
116    let prestate = cache_state.clone();
117    let mut state =
118        database::State::builder().with_cached_prestate(prestate).with_bundle_update().build();
119
120    // SAFETY: The handler and evm do not outlive `state`. The `'static` in
121    // `StateTestEvm<'static>` is required by the `Handler` trait but we
122    // guarantee the borrow is valid for the duration of `handler.run`.
123    let exec_result = unsafe {
124        let db_ref = &mut *(&mut state as *mut database::State<EmptyDB>);
125        let evm_context = Context::mainnet()
126            .with_block(block.clone())
127            .with_tx(tx.clone())
128            .with_cfg(cfg.clone())
129            .with_db(db_ref);
130        let mut handler = crate::compiled::CompiledHandler { compiled, cache, spec_id };
131        let mut evm = evm_context.build_mainnet();
132        let result = handler.run(&mut evm);
133        if result.is_ok() {
134            let s = evm.ctx.journaled_state.finalize();
135            DatabaseCommit::commit(&mut evm.ctx.journaled_state.database, s);
136        }
137        result
138    };
139    let db = &state;
140
141    snapshot_from_result(&exec_result, db)
142}
143
144/// Re-run a test case with the interpreter and EIP-3155 tracing to stderr.
145pub fn trace_interpreter(
146    cfg: &CfgEnv,
147    block: &BlockEnv,
148    tx: &TxEnv,
149    cache_state: &database::CacheState,
150) {
151    let prestate = cache_state.clone();
152    let mut state =
153        database::State::builder().with_cached_prestate(prestate).with_bundle_update().build();
154
155    let mut evm = Context::mainnet()
156        .with_db(&mut state)
157        .with_block(block)
158        .with_tx(tx)
159        .with_cfg(cfg.clone())
160        .build_mainnet_with_inspector(TracerEip3155::buffered(stderr()).without_summary());
161    let exec_result = evm.inspect_tx_commit(tx);
162
163    eprintln!("\nExecution result: {exec_result:#?}");
164    eprintln!("\nState after:\n{}", evm.ctx.journaled_state.database.cache.pretty_print());
165}
166
167/// Compare two snapshots and return a list of mismatched fields.
168pub fn compare(interp: &ExecutionSnapshot, jit: &ExecutionSnapshot) -> Vec<Mismatch> {
169    let mut mismatches = Vec::new();
170
171    let interp_status = format!("{}", interp.status);
172    let jit_status = format!("{}", jit.status);
173    if interp_status != jit_status {
174        mismatches.push(Mismatch { field: "status", interpreter: interp_status, jit: jit_status });
175    }
176
177    if interp.output != jit.output {
178        mismatches.push(Mismatch {
179            field: "output",
180            interpreter: format!("{:?}", interp.output),
181            jit: format!("{:?}", jit.output),
182        });
183    }
184
185    if interp.gas_used != jit.gas_used {
186        mismatches.push(Mismatch {
187            field: "gas_used",
188            interpreter: interp.gas_used.to_string(),
189            jit: jit.gas_used.to_string(),
190        });
191    }
192
193    if interp.state_root != jit.state_root {
194        mismatches.push(Mismatch {
195            field: "state_root",
196            interpreter: format!("{}", interp.state_root),
197            jit: format!("{}", jit.state_root),
198        });
199    }
200
201    if interp.logs_root != jit.logs_root {
202        mismatches.push(Mismatch {
203            field: "logs_root",
204            interpreter: format!("{}", interp.logs_root),
205            jit: format!("{}", jit.logs_root),
206        });
207    }
208
209    mismatches
210}
211
212/// A single field mismatch between interpreter and JIT execution.
213pub struct Mismatch {
214    pub field: &'static str,
215    pub interpreter: String,
216    pub jit: String,
217}
218
219impl std::fmt::Display for Mismatch {
220    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221        write!(f, "  {}: interpreter={}, jit={}", self.field, self.interpreter, self.jit)
222    }
223}