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