1use crate::merkle_trie::{TestValidationResult, compute_test_roots};
5use console::Term;
6use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
7use revm_context::{Context, block::BlockEnv, cfg::CfgEnv, tx::TxEnv};
8use revm_context_interface::result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction};
9use revm_database::{self as database, bal::EvmDatabaseError};
10use revm_database_interface::EmptyDB;
11use revm_handler::{ExecuteCommitEvm, MainBuilder, MainContext};
12use revm_primitives::{B256, Bytes, U256, hardfork::SpecId};
13use revm_statetest_types::{SpecName, Test, TestSuite, TestUnit};
14use serde_json::json;
15use std::{
16 convert::Infallible,
17 fmt::Debug,
18 path::{Path, PathBuf},
19 sync::{
20 Arc, Mutex,
21 atomic::{AtomicBool, AtomicUsize, Ordering},
22 },
23 time::{Duration, Instant},
24};
25use thiserror::Error;
26
27#[derive(Debug, Error)]
29#[error("Path: {path}\nName: {name}\nError: {kind}")]
30pub struct TestError {
31 pub name: String,
32 pub path: String,
33 pub kind: TestErrorKind,
34}
35
36#[derive(Debug, Error)]
38pub enum TestErrorKind {
39 #[error("logs root mismatch: got {got}, expected {expected}")]
40 LogsRootMismatch { got: B256, expected: B256 },
41 #[error("state root mismatch: got {got}, expected {expected}")]
42 StateRootMismatch { got: B256, expected: B256 },
43 #[error("unknown private key: {0:?}")]
44 UnknownPrivateKey(B256),
45 #[error("unexpected exception: got {got_exception:?}, expected {expected_exception:?}")]
46 UnexpectedException { expected_exception: Option<String>, got_exception: Option<String> },
47 #[error("unexpected output: got {got_output:?}, expected {expected_output:?}")]
48 UnexpectedOutput { expected_output: Option<Bytes>, got_output: Option<Bytes> },
49 #[error(transparent)]
50 SerdeDeserialize(#[from] serde_json::Error),
51 #[error("thread panicked")]
52 Panic,
53 #[error("path does not exist")]
54 InvalidPath,
55 #[error("no JSON test files found in path")]
56 NoJsonFiles,
57 #[error("compilation failed: {0}")]
58 CompilationError(String),
59}
60
61pub fn skip_test(path: &Path) -> bool {
64 let path_str = path.to_str().unwrap_or_default();
65
66 if path_str.contains("paris/eip7610_create_collision") {
68 return true;
69 }
70
71 let name = path.file_name().unwrap().to_str().unwrap_or_default();
72
73 matches!(
74 name,
75 | "CreateTransactionHighNonce.json"
78
79 | "RevertInCreateInInit_Paris.json"
81 | "RevertInCreateInInit.json"
82 | "dynamicAccountOverwriteEmpty.json"
83 | "dynamicAccountOverwriteEmpty_Paris.json"
84 | "RevertInCreateInInitCreate2Paris.json"
85 | "create2collisionStorage.json"
86 | "RevertInCreateInInitCreate2.json"
87 | "create2collisionStorageParis.json"
88 | "InitCollision.json"
89 | "InitCollisionParis.json"
90 | "test_init_collision_create_opcode.json"
91
92 | "ValueOverflow.json"
94 | "ValueOverflowParis.json"
95
96 | "Call50000_sha256.json"
98 | "static_Call50000_sha256.json"
99 | "loopMul.json"
100 | "CALLBlake2f_MaxRounds.json"
101 )
102}
103
104struct TestExecutionContext<'a> {
105 name: &'a str,
106 unit: &'a TestUnit,
107 test: &'a Test,
108 cfg: &'a CfgEnv,
109 block: &'a BlockEnv,
110 tx: &'a TxEnv,
111 cache_state: &'a database::CacheState,
112 elapsed: &'a Arc<Mutex<Duration>>,
113 #[allow(dead_code)]
114 trace: bool,
115 print_json_outcome: bool,
116}
117
118fn build_json_output(
119 test: &Test,
120 test_name: &str,
121 exec_result: &Result<
122 ExecutionResult<HaltReason>,
123 EVMError<EvmDatabaseError<Infallible>, InvalidTransaction>,
124 >,
125 validation: &TestValidationResult,
126 spec: SpecId,
127 error: Option<String>,
128) -> serde_json::Value {
129 json!({
130 "stateRoot": validation.state_root,
131 "logsRoot": validation.logs_root,
132 "output": exec_result.as_ref().ok().and_then(|r| r.output().cloned()).unwrap_or_default(),
133 "gasUsed": exec_result.as_ref().ok().map(|r| r.tx_gas_used()).unwrap_or_default(),
134 "pass": error.is_none(),
135 "errorMsg": error.unwrap_or_default(),
136 "evmResult": format_evm_result(exec_result),
137 "postLogsHash": validation.logs_root,
138 "fork": spec,
139 "test": test_name,
140 "d": test.indexes.data,
141 "g": test.indexes.gas,
142 "v": test.indexes.value,
143 })
144}
145
146fn format_evm_result(
147 exec_result: &Result<
148 ExecutionResult<HaltReason>,
149 EVMError<EvmDatabaseError<Infallible>, InvalidTransaction>,
150 >,
151) -> String {
152 match exec_result {
153 Ok(r) => match r {
154 ExecutionResult::Success { reason, .. } => format!("Success: {reason:?}"),
155 ExecutionResult::Revert { .. } => "Revert".to_string(),
156 ExecutionResult::Halt { reason, .. } => format!("Halt: {reason:?}"),
157 },
158 Err(e) => e.to_string(),
159 }
160}
161
162fn validate_exception(
163 test: &Test,
164 exec_result: &Result<
165 ExecutionResult<HaltReason>,
166 EVMError<EvmDatabaseError<Infallible>, InvalidTransaction>,
167 >,
168) -> Result<bool, TestErrorKind> {
169 match (&test.expect_exception, exec_result) {
170 (None, Ok(_)) => Ok(false),
171 (Some(_), Err(_)) => Ok(true),
172 _ => Err(TestErrorKind::UnexpectedException {
173 expected_exception: test.expect_exception.clone(),
174 got_exception: exec_result.as_ref().err().map(|e| e.to_string()),
175 }),
176 }
177}
178
179fn validate_output(
180 expected_output: Option<&Bytes>,
181 actual_result: &ExecutionResult<HaltReason>,
182) -> Result<(), TestErrorKind> {
183 if let Some((expected, actual)) = expected_output.zip(actual_result.output()) {
184 if expected != actual {
185 return Err(TestErrorKind::UnexpectedOutput {
186 expected_output: Some(expected.clone()),
187 got_output: actual_result.output().cloned(),
188 });
189 }
190 }
191 Ok(())
192}
193
194pub(crate) fn check_evm_execution(
195 test: &Test,
196 expected_output: Option<&Bytes>,
197 test_name: &str,
198 exec_result: &Result<
199 ExecutionResult<HaltReason>,
200 EVMError<EvmDatabaseError<Infallible>, InvalidTransaction>,
201 >,
202 db: &mut database::State<EmptyDB>,
203 spec: SpecId,
204 print_json_outcome: bool,
205) -> Result<(), TestErrorKind> {
206 let validation = compute_test_roots(exec_result, db);
207
208 let print_json = |error: Option<&TestErrorKind>| {
209 if print_json_outcome {
210 let json = build_json_output(
211 test,
212 test_name,
213 exec_result,
214 &validation,
215 spec,
216 error.map(|e| e.to_string()),
217 );
218 eprintln!("{json}");
219 }
220 };
221
222 let exception_expected = validate_exception(test, exec_result).inspect_err(|e| {
224 print_json(Some(e));
225 })?;
226
227 if exception_expected {
229 print_json(None);
230 return Ok(());
231 }
232
233 if let Ok(result) = exec_result {
235 validate_output(expected_output, result).inspect_err(|e| {
236 print_json(Some(e));
237 })?;
238 }
239
240 if validation.logs_root != test.logs {
242 let error =
243 TestErrorKind::LogsRootMismatch { got: validation.logs_root, expected: test.logs };
244 print_json(Some(&error));
245 return Err(error);
246 }
247
248 if validation.state_root != test.hash {
250 let error =
251 TestErrorKind::StateRootMismatch { got: validation.state_root, expected: test.hash };
252 print_json(Some(&error));
253 return Err(error);
254 }
255
256 print_json(None);
257 Ok(())
258}
259
260pub fn execute_test_suite(
262 path: &Path,
263 elapsed: &Arc<Mutex<Duration>>,
264 trace: bool,
265 print_json_outcome: bool,
266) -> Result<(), TestError> {
267 if skip_test(path) {
268 return Ok(());
269 }
270
271 let s = std::fs::read_to_string(path).unwrap();
272 let path = path.to_string_lossy().into_owned();
273 let suite: TestSuite = serde_json::from_str(&s).map_err(|e| TestError {
274 name: "Unknown".to_string(),
275 path: path.clone(),
276 kind: e.into(),
277 })?;
278
279 for (name, unit) in suite.0 {
280 let cache_state = unit.state();
282
283 let mut cfg = CfgEnv::default();
285 cfg.chain_id = unit.env.current_chain_id.unwrap_or(U256::ONE).try_into().unwrap_or(1);
286
287 for (spec_name, tests) in &unit.post {
289 if *spec_name == SpecName::Constantinople {
291 continue;
292 }
293
294 cfg.set_spec_and_mainnet_gas_params(spec_name.to_spec_id());
295
296 if cfg.spec().is_enabled_in(SpecId::OSAKA) {
298 cfg.set_max_blobs_per_tx(6);
299 } else if cfg.spec().is_enabled_in(SpecId::PRAGUE) {
300 cfg.set_max_blobs_per_tx(9);
301 } else {
302 cfg.set_max_blobs_per_tx(6);
303 }
304
305 let block = unit.block_env(&mut cfg);
307
308 for test in tests.iter() {
309 let tx = match test.tx_env(&unit) {
311 Ok(tx) => tx,
312 Err(_) if test.expect_exception.is_some() => continue,
313 Err(_) => {
314 return Err(TestError {
315 name,
316 path,
317 kind: TestErrorKind::UnknownPrivateKey(unit.transaction.secret_key),
318 });
319 }
320 };
321
322 let result = execute_single_test(TestExecutionContext {
324 name: &name,
325 unit: &unit,
326 test,
327 cfg: &cfg,
328 block: &block,
329 tx: &tx,
330 cache_state: &cache_state,
331 elapsed,
332 trace,
333 print_json_outcome,
334 });
335
336 if let Err(e) = result {
337 static FAILED: AtomicBool = AtomicBool::new(false);
339 if print_json_outcome || FAILED.swap(true, Ordering::SeqCst) {
340 return Err(TestError { name, path, kind: e });
341 }
342
343 return Err(TestError { path, name, kind: e });
344 }
345 }
346 }
347 }
348 Ok(())
349}
350
351fn execute_single_test(ctx: TestExecutionContext) -> Result<(), TestErrorKind> {
352 let cache = ctx.cache_state.clone();
354 let mut state =
355 database::State::builder().with_cached_prestate(cache).with_bundle_update().build();
356
357 let evm_context = Context::mainnet()
358 .with_block(ctx.block)
359 .with_tx(ctx.tx)
360 .with_cfg(ctx.cfg.clone())
361 .with_db(&mut state);
362
363 let timer = Instant::now();
365 let mut evm = evm_context.build_mainnet();
366 let exec_result = evm.transact_commit(ctx.tx);
367 let db = evm.ctx.journaled_state.database;
368 *ctx.elapsed.lock().unwrap() += timer.elapsed();
369
370 check_evm_execution(
372 ctx.test,
373 ctx.unit.out.as_ref(),
374 ctx.name,
375 &exec_result,
376 db,
377 *ctx.cfg.spec(),
378 ctx.print_json_outcome,
379 )
380}
381
382#[derive(Clone)]
383pub(crate) struct TestRunnerState {
384 pub(crate) n_errors: Arc<AtomicUsize>,
385 pub(crate) console_bar: Arc<ProgressBar>,
386 pub(crate) queue: Arc<Mutex<(usize, Vec<PathBuf>)>>,
387 pub(crate) elapsed: Arc<Mutex<Duration>>,
388 pub(crate) stop: Arc<AtomicBool>,
390}
391
392fn console_bar(n_files: usize) -> ProgressBar {
393 let bar = ProgressBar::with_draw_target(
394 Some(n_files as u64),
395 ProgressDrawTarget::term_like_with_hz(Box::new(Term::buffered_stderr()), 1),
396 );
397 bar.set_style(
398 ProgressStyle::with_template(
399 "[{elapsed_precise}] {wide_bar} {pos}/{len} ({per_sec}, eta {eta})",
400 )
401 .unwrap()
402 .progress_chars("=>-"),
403 );
404 bar.enable_steady_tick(Duration::from_secs(1));
405 bar
406}
407
408impl TestRunnerState {
409 pub(crate) fn new(test_files: Vec<PathBuf>) -> Self {
410 let n_files = test_files.len();
411 Self {
412 n_errors: Arc::new(AtomicUsize::new(0)),
413 console_bar: Arc::new(console_bar(n_files)),
414 queue: Arc::new(Mutex::new((0usize, test_files))),
415 elapsed: Arc::new(Mutex::new(Duration::ZERO)),
416 stop: Arc::new(AtomicBool::new(false)),
417 }
418 }
419
420 pub(crate) fn next_test(&self) -> Option<PathBuf> {
421 if self.stop.load(Ordering::Relaxed) {
422 return None;
423 }
424 let (current_idx, queue) = &mut *self.queue.lock().unwrap();
425 let idx = *current_idx;
426 let test_path = queue.get(idx).cloned()?;
427 *current_idx = idx + 1;
428 Some(test_path)
429 }
430}