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