1use super::{
2 merkle_trie::{log_rlp_hash, state_merkle_trie_root},
3 utils::recover_address,
4};
5use database::State;
6use indicatif::{ProgressBar, ProgressDrawTarget};
7use inspector::{inspectors::TracerEip3155, InspectCommitEvm};
8use revm::{
9 bytecode::Bytecode,
10 context::{block::BlockEnv, cfg::CfgEnv, tx::TxEnv},
11 context_interface::{
12 block::calc_excess_blob_gas,
13 result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction},
14 Cfg,
15 },
16 database_interface::EmptyDB,
17 primitives::{
18 eip4844::TARGET_BLOB_GAS_PER_BLOCK_CANCUN, hardfork::SpecId, keccak256, Bytes, TxKind, B256,
19 },
20 Context, ExecuteCommitEvm, MainBuilder, MainContext,
21};
22use serde_json::json;
23use statetest_types::{SpecName, Test, TestSuite};
24
25use std::{
26 convert::Infallible,
27 fmt::Debug,
28 io::stderr,
29 path::{Path, PathBuf},
30 sync::{
31 atomic::{AtomicBool, AtomicUsize, Ordering},
32 Arc, Mutex,
33 },
34 time::{Duration, Instant},
35};
36use thiserror::Error;
37use walkdir::{DirEntry, WalkDir};
38
39#[derive(Debug, Error)]
40#[error("Path: {path}\nName: {name}\nError: {kind}")]
41pub struct TestError {
42 pub name: String,
43 pub path: String,
44 pub kind: TestErrorKind,
45}
46
47#[derive(Debug, Error)]
48pub enum TestErrorKind {
49 #[error("logs root mismatch: got {got}, expected {expected}")]
50 LogsRootMismatch { got: B256, expected: B256 },
51 #[error("state root mismatch: got {got}, expected {expected}")]
52 StateRootMismatch { got: B256, expected: B256 },
53 #[error("unknown private key: {0:?}")]
54 UnknownPrivateKey(B256),
55 #[error("unexpected exception: got {got_exception:?}, expected {expected_exception:?}")]
56 UnexpectedException {
57 expected_exception: Option<String>,
58 got_exception: Option<String>,
59 },
60 #[error("unexpected output: got {got_output:?}, expected {expected_output:?}")]
61 UnexpectedOutput {
62 expected_output: Option<Bytes>,
63 got_output: Option<Bytes>,
64 },
65 #[error(transparent)]
66 SerdeDeserialize(#[from] serde_json::Error),
67 #[error("thread panicked")]
68 Panic,
69 #[error("path does not exist")]
70 InvalidPath,
71 #[error("no JSON test files found in path")]
72 NoJsonFiles,
73}
74
75pub fn find_all_json_tests(path: &Path) -> Vec<PathBuf> {
76 if path.is_file() {
77 vec![path.to_path_buf()]
78 } else {
79 WalkDir::new(path)
80 .into_iter()
81 .filter_map(Result::ok)
82 .filter(|e| e.path().extension() == Some("json".as_ref()))
83 .map(DirEntry::into_path)
84 .collect()
85 }
86}
87
88fn skip_test(path: &Path) -> bool {
89 let name = path.file_name().unwrap().to_str().unwrap();
90
91 matches!(
92 name,
93 |"ValueOverflow.json"| "ValueOverflowParis.json"
96
97 | "RevertPrecompiledTouch_storage.json"
99 | "RevertPrecompiledTouch.json"
100
101 | "typeTwoBerlin.json"
103
104 | "transactionIntinsicBug.json"
106
107 | "HighGasPrice.json"
109 | "CREATE_HighNonce.json"
110 | "CREATE_HighNonceMinus1.json"
111 | "CreateTransactionHighNonce.json"
112
113 | "basefeeExample.json"
117 | "eip1559.json"
118 | "mergeTest.json"
119
120 | "RevertInCreateInInit_Paris.json"
122 | "RevertInCreateInInit.json"
123 | "dynamicAccountOverwriteEmpty.json"
124 | "dynamicAccountOverwriteEmpty_Paris.json"
125 | "RevertInCreateInInitCreate2Paris.json"
126 | "create2collisionStorage.json"
127 | "RevertInCreateInInitCreate2.json"
128 | "create2collisionStorageParis.json"
129 | "InitCollision.json"
130 | "InitCollisionParis.json"
131
132 | "loopExp.json"
134 | "Call50000_sha256.json"
135 | "static_Call50000_sha256.json"
136 | "loopMul.json"
137 | "CALLBlake2f_MaxRounds.json"
138 )
139}
140
141fn check_evm_execution(
142 test: &Test,
143 expected_output: Option<&Bytes>,
144 test_name: &str,
145 exec_result: &Result<ExecutionResult<HaltReason>, EVMError<Infallible, InvalidTransaction>>,
146 db: &mut State<EmptyDB>,
147 spec: SpecId,
148 print_json_outcome: bool,
149) -> Result<(), TestErrorKind> {
150 let logs_root = log_rlp_hash(exec_result.as_ref().map(|r| r.logs()).unwrap_or_default());
151 let state_root = state_merkle_trie_root(db.cache.trie_account());
152
153 let print_json_output = |error: Option<String>| {
154 if print_json_outcome {
155 let json = json!({
156 "stateRoot": state_root,
157 "logsRoot": logs_root,
158 "output": exec_result.as_ref().ok().and_then(|r| r.output().cloned()).unwrap_or_default(),
159 "gasUsed": exec_result.as_ref().ok().map(|r| r.gas_used()).unwrap_or_default(),
160 "pass": error.is_none(),
161 "errorMsg": error.unwrap_or_default(),
162 "evmResult": match exec_result {
163 Ok(r) => match r {
164 ExecutionResult::Success { reason, .. } => format!("Success: {reason:?}"),
165 ExecutionResult::Revert { .. } => "Revert".to_string(),
166 ExecutionResult::Halt { reason, .. } => format!("Halt: {reason:?}"),
167 },
168 Err(e) => e.to_string(),
169 },
170 "postLogsHash": logs_root,
171 "fork": spec,
172 "test": test_name,
173 "d": test.indexes.data,
174 "g": test.indexes.gas,
175 "v": test.indexes.value,
176 });
177 eprintln!("{json}");
178 }
179 };
180
181 match (&test.expect_exception, exec_result) {
192 (None, Ok(result)) => {
194 if let Some((expected_output, output)) = expected_output.zip(result.output()) {
196 if expected_output != output {
197 let kind = TestErrorKind::UnexpectedOutput {
198 expected_output: Some(expected_output.clone()),
199 got_output: result.output().cloned(),
200 };
201 print_json_output(Some(kind.to_string()));
202 return Err(kind);
203 }
204 }
205 }
206 (Some(_), Err(_)) => return Ok(()),
208 _ => {
209 let kind = TestErrorKind::UnexpectedException {
210 expected_exception: test.expect_exception.clone(),
211 got_exception: exec_result.clone().err().map(|e| e.to_string()),
212 };
213 print_json_output(Some(kind.to_string()));
214 return Err(kind);
215 }
216 }
217
218 if logs_root != test.logs {
219 let kind = TestErrorKind::LogsRootMismatch {
220 got: logs_root,
221 expected: test.logs,
222 };
223 print_json_output(Some(kind.to_string()));
224 return Err(kind);
225 }
226
227 if state_root != test.hash {
228 let kind = TestErrorKind::StateRootMismatch {
229 got: state_root,
230 expected: test.hash,
231 };
232 print_json_output(Some(kind.to_string()));
233 return Err(kind);
234 }
235
236 print_json_output(None);
237
238 Ok(())
239}
240
241pub fn execute_test_suite(
242 path: &Path,
243 elapsed: &Arc<Mutex<Duration>>,
244 trace: bool,
245 print_json_outcome: bool,
246) -> Result<(), TestError> {
247 if skip_test(path) {
248 return Ok(());
249 }
250
251 let s = std::fs::read_to_string(path).unwrap();
252 let path = path.to_string_lossy().into_owned();
253 let suite: TestSuite = serde_json::from_str(&s).map_err(|e| TestError {
254 name: "Unknown".to_string(),
255 path: path.clone(),
256 kind: e.into(),
257 })?;
258
259 for (name, unit) in suite.0 {
260 let mut cache_state = database::CacheState::new(false);
262 for (address, info) in unit.pre {
263 let code_hash = keccak256(&info.code);
264 let bytecode = Bytecode::new_raw_checked(info.code.clone())
265 .unwrap_or(Bytecode::new_legacy(info.code));
266 let acc_info = revm::state::AccountInfo {
267 balance: info.balance,
268 code_hash,
269 code: Some(bytecode),
270 nonce: info.nonce,
271 };
272 cache_state.insert_account_with_storage(address, acc_info, info.storage);
273 }
274
275 let mut cfg = CfgEnv::default();
276 let mut block = BlockEnv::default();
277 let mut tx = TxEnv::default();
278 cfg.chain_id = 1;
280
281 block.number = unit.env.current_number.try_into().unwrap_or(u64::MAX);
283 block.beneficiary = unit.env.current_coinbase;
284 block.timestamp = unit.env.current_timestamp.try_into().unwrap_or(u64::MAX);
285 block.gas_limit = unit.env.current_gas_limit.try_into().unwrap_or(u64::MAX);
286 block.basefee = unit
287 .env
288 .current_base_fee
289 .unwrap_or_default()
290 .try_into()
291 .unwrap_or(u64::MAX);
292 block.difficulty = unit.env.current_difficulty;
293 block.prevrandao = unit.env.current_random;
295
296 tx.caller = if let Some(address) = unit.transaction.sender {
298 address
299 } else {
300 recover_address(unit.transaction.secret_key.as_slice()).ok_or_else(|| TestError {
301 name: name.clone(),
302 path: path.clone(),
303 kind: TestErrorKind::UnknownPrivateKey(unit.transaction.secret_key),
304 })?
305 };
306 tx.gas_price = unit
307 .transaction
308 .gas_price
309 .or(unit.transaction.max_fee_per_gas)
310 .unwrap_or_default()
311 .try_into()
312 .unwrap_or(u128::MAX);
313 tx.gas_priority_fee = unit
314 .transaction
315 .max_priority_fee_per_gas
316 .map(|b| u128::try_from(b).expect("max priority fee less than u128::MAX"));
317 tx.blob_hashes = unit.transaction.blob_versioned_hashes.clone();
319 tx.max_fee_per_blob_gas = unit
320 .transaction
321 .max_fee_per_blob_gas
322 .map(|b| u128::try_from(b).expect("max fee less than u128::MAX"))
323 .unwrap_or(u128::MAX);
324
325 for (spec_name, tests) in unit.post {
327 if spec_name == SpecName::Constantinople {
331 continue;
332 }
333
334 cfg.spec = spec_name.to_spec_id();
335
336 if let Some(current_excess_blob_gas) = unit.env.current_excess_blob_gas {
338 block.set_blob_excess_gas_and_price(
339 current_excess_blob_gas.to(),
340 cfg.spec.is_enabled_in(SpecId::PRAGUE),
341 );
342 } else if let (Some(parent_blob_gas_used), Some(parent_excess_blob_gas)) = (
343 unit.env.parent_blob_gas_used,
344 unit.env.parent_excess_blob_gas,
345 ) {
346 block.set_blob_excess_gas_and_price(
347 calc_excess_blob_gas(
348 parent_blob_gas_used.to(),
349 parent_excess_blob_gas.to(),
350 unit.env
351 .parent_target_blobs_per_block
352 .map(|i| i.to())
353 .unwrap_or(TARGET_BLOB_GAS_PER_BLOCK_CANCUN),
354 ),
355 cfg.spec.is_enabled_in(SpecId::PRAGUE),
356 );
357 }
358
359 if cfg.spec.is_enabled_in(SpecId::MERGE) && block.prevrandao.is_none() {
360 block.prevrandao = Some(B256::default());
362 }
363
364 for (index, test) in tests.into_iter().enumerate() {
365 let Some(tx_type) = unit.transaction.tx_type(test.indexes.data) else {
366 if test.expect_exception.is_some() {
367 continue;
368 } else {
369 panic!("Invalid transaction type without expected exception");
370 }
371 };
372
373 tx.tx_type = tx_type as u8;
374
375 tx.gas_limit = unit.transaction.gas_limit[test.indexes.gas].saturating_to();
376
377 tx.data = unit
378 .transaction
379 .data
380 .get(test.indexes.data)
381 .unwrap()
382 .clone();
383
384 tx.nonce = u64::try_from(unit.transaction.nonce).unwrap();
385 tx.value = unit.transaction.value[test.indexes.value];
386
387 tx.access_list = unit
388 .transaction
389 .access_lists
390 .get(test.indexes.data)
391 .cloned()
392 .flatten()
393 .unwrap_or_default();
394
395 tx.authorization_list = unit
396 .transaction
397 .authorization_list
398 .clone()
399 .map(|auth_list| auth_list.into_iter().map(Into::into).collect::<Vec<_>>())
400 .unwrap_or_default();
401
402 let to = match unit.transaction.to {
403 Some(add) => TxKind::Call(add),
404 None => TxKind::Create,
405 };
406 tx.kind = to;
407
408 let mut cache = cache_state.clone();
409 cache.set_state_clear_flag(cfg.spec.is_enabled_in(SpecId::SPURIOUS_DRAGON));
410 let mut state = database::State::builder()
411 .with_cached_prestate(cache)
412 .with_bundle_update()
413 .build();
414 let mut evm = Context::mainnet()
415 .with_block(&block)
416 .with_tx(&tx)
417 .with_cfg(&cfg)
418 .with_db(&mut state)
419 .build_mainnet();
420
421 let (e, exec_result) = if trace {
423 let mut evm = Context::mainnet()
424 .with_block(&block)
425 .with_tx(&tx)
426 .with_cfg(&cfg)
427 .with_db(&mut state)
428 .build_mainnet_with_inspector(
429 TracerEip3155::buffered(stderr()).without_summary(),
430 );
431
432 let timer = Instant::now();
433 let res = evm.inspect_replay_commit();
434 *elapsed.lock().unwrap() += timer.elapsed();
435
436 let spec = cfg.spec();
437 let db = &mut evm.data.ctx.journaled_state.database;
438 let output = check_evm_execution(
440 &test,
441 unit.out.as_ref(),
442 &name,
443 &res,
444 db,
445 spec,
446 print_json_outcome,
447 );
448 let Err(e) = output else {
449 continue;
450 };
451 (e, res)
452 } else {
453 let timer = Instant::now();
454 let res = evm.replay_commit();
455 *elapsed.lock().unwrap() += timer.elapsed();
456
457 let spec = cfg.spec();
458 let db = evm.data.ctx.journaled_state.database;
459 let output = check_evm_execution(
461 &test,
462 unit.out.as_ref(),
463 &name,
464 &res,
465 db,
466 spec,
467 print_json_outcome,
468 );
469 let Err(e) = output else {
470 continue;
471 };
472 (e, res)
473 };
474
475 static FAILED: AtomicBool = AtomicBool::new(false);
478 if print_json_outcome || FAILED.swap(true, Ordering::SeqCst) {
479 return Err(TestError {
480 name: name.clone(),
481 path: path.clone(),
482 kind: e,
483 });
484 }
485
486 let mut cache = cache_state.clone();
488 cache.set_state_clear_flag(cfg.spec.is_enabled_in(SpecId::SPURIOUS_DRAGON));
489 let mut state = database::State::builder()
490 .with_cached_prestate(cache)
491 .with_bundle_update()
492 .build();
493
494 println!("\nTraces:");
495
496 let mut evm = Context::mainnet()
497 .with_db(&mut state)
498 .with_block(&block)
499 .with_tx(&tx)
500 .with_cfg(&cfg)
501 .build_mainnet_with_inspector(
502 TracerEip3155::buffered(stderr()).without_summary(),
503 );
504
505 let _ = evm.inspect_replay_commit();
506
507 println!("\nExecution result: {exec_result:#?}");
508 println!("\nExpected exception: {:?}", test.expect_exception);
509 println!("\nState before: {cache_state:#?}");
510 println!(
511 "\nState after: {:#?}",
512 evm.data.ctx.journaled_state.database.cache
513 );
514 println!("\nSpecification: {:?}", cfg.spec);
515 println!("\nTx: {tx:#?}");
516 println!("Block: {block:#?}");
517 println!("Cfg: {cfg:#?}");
518 println!("\nTest name: {name:?} (index: {index}, path: {path:?}) failed:\n{e}");
519
520 return Err(TestError {
521 path: path.clone(),
522 name: name.clone(),
523 kind: e,
524 });
525 }
526 }
527 }
528 Ok(())
529}
530
531pub fn run(
532 test_files: Vec<PathBuf>,
533 mut single_thread: bool,
534 trace: bool,
535 mut print_outcome: bool,
536 keep_going: bool,
537) -> Result<(), TestError> {
538 if trace {
540 print_outcome = true;
541 }
542 if print_outcome {
544 single_thread = true;
545 }
546 let n_files = test_files.len();
547
548 let n_errors = Arc::new(AtomicUsize::new(0));
549 let console_bar = Arc::new(ProgressBar::with_draw_target(
550 Some(n_files as u64),
551 ProgressDrawTarget::stdout(),
552 ));
553 let queue = Arc::new(Mutex::new((0usize, test_files)));
554 let elapsed = Arc::new(Mutex::new(std::time::Duration::ZERO));
555
556 let num_threads = match (single_thread, std::thread::available_parallelism()) {
557 (true, _) | (false, Err(_)) => 1,
558 (false, Ok(n)) => n.get(),
559 };
560 let num_threads = num_threads.min(n_files);
561 let mut handles = Vec::with_capacity(num_threads);
562 for i in 0..num_threads {
563 let queue = queue.clone();
564 let n_errors = n_errors.clone();
565 let console_bar = console_bar.clone();
566 let elapsed = elapsed.clone();
567
568 let thread = std::thread::Builder::new().name(format!("runner-{i}"));
569
570 let f = move || loop {
571 if !keep_going && n_errors.load(Ordering::SeqCst) > 0 {
572 return Ok(());
573 }
574
575 let (_index, test_path) = {
576 let (current_idx, queue) = &mut *queue.lock().unwrap();
577 let prev_idx = *current_idx;
578 let Some(test_path) = queue.get(prev_idx).cloned() else {
579 return Ok(());
580 };
581 *current_idx = prev_idx + 1;
582 (prev_idx, test_path)
583 };
584
585 let result = execute_test_suite(&test_path, &elapsed, trace, print_outcome);
586
587 console_bar.inc(1);
589
590 if let Err(err) = result {
591 n_errors.fetch_add(1, Ordering::SeqCst);
592 if !keep_going {
593 return Err(err);
594 }
595 }
596 };
597 handles.push(thread.spawn(f).unwrap());
598 }
599
600 let mut thread_errors = Vec::new();
602 for (i, handle) in handles.into_iter().enumerate() {
603 match handle.join() {
604 Ok(Ok(())) => {}
605 Ok(Err(e)) => thread_errors.push(e),
606 Err(_) => thread_errors.push(TestError {
607 name: format!("thread {i} panicked"),
608 path: "".to_string(),
609 kind: TestErrorKind::Panic,
610 }),
611 }
612 }
613 console_bar.finish();
614
615 println!(
616 "Finished execution. Total CPU time: {:.6}s",
617 elapsed.lock().unwrap().as_secs_f64()
618 );
619
620 let n_errors = n_errors.load(Ordering::SeqCst);
621 let n_thread_errors = thread_errors.len();
622 if n_errors == 0 && n_thread_errors == 0 {
623 println!("All tests passed!");
624 Ok(())
625 } else {
626 println!("Encountered {n_errors} errors out of {n_files} total tests");
627
628 if n_thread_errors == 0 {
629 std::process::exit(1);
630 }
631
632 if n_thread_errors > 1 {
633 println!("{n_thread_errors} threads returned an error, out of {num_threads} total:");
634 for error in &thread_errors {
635 println!("{error}");
636 }
637 }
638 Err(thread_errors.swap_remove(0))
639 }
640}