1pub mod post_block;
2pub mod pre_block;
3
4use clap::Parser;
5use context::ContextTr;
6use context_interface::block::BlobExcessGasAndPrice;
7use database::states::bundle_state::BundleRetention;
8use database::{EmptyDB, State};
9use inspector::inspectors::TracerEip3155;
10use primitives::{hardfork::SpecId, hex, Address, HashMap, U256};
11use revm::handler::EvmTr;
12use revm::{
13 context::cfg::CfgEnv, context_interface::result::HaltReason, Context, MainBuilder, MainContext,
14};
15use revm::{Database, ExecuteCommitEvm, ExecuteEvm, InspectEvm};
16use serde_json::json;
17use state::AccountInfo;
18use statetest_types::blockchain::{
19 Account, BlockchainTest, BlockchainTestCase, ForkSpec, Withdrawal,
20};
21use std::collections::BTreeMap;
22use std::fs;
23use std::path::{Path, PathBuf};
24use std::time::Instant;
25use thiserror::Error;
26use walkdir::{DirEntry, WalkDir};
27
28#[derive(Parser, Debug)]
30pub struct Cmd {
31 #[arg(required = true, num_args = 1..)]
37 paths: Vec<PathBuf>,
38 #[arg(long)]
40 omit_progress: bool,
41 #[arg(long, alias = "no-fail-fast")]
43 keep_going: bool,
44 #[arg(long)]
46 print_env_on_error: bool,
47 #[arg(long)]
49 json: bool,
50}
51
52impl Cmd {
53 pub fn run(&self) -> Result<(), Error> {
55 for path in &self.paths {
56 if !path.exists() {
57 return Err(Error::PathNotFound(path.clone()));
58 }
59
60 if !self.json {
61 println!("\nRunning blockchain tests in {}...", path.display());
62 }
63 let test_files = find_all_json_tests(path);
64
65 if test_files.is_empty() {
66 return Err(Error::NoJsonFiles(path.clone()));
67 }
68
69 run_tests(
70 test_files,
71 self.omit_progress,
72 self.keep_going,
73 self.print_env_on_error,
74 self.json,
75 )?;
76 }
77 Ok(())
78 }
79}
80
81pub fn find_all_json_tests(path: &Path) -> Vec<PathBuf> {
85 if path.is_file() {
86 vec![path.to_path_buf()]
87 } else {
88 WalkDir::new(path)
89 .into_iter()
90 .filter_map(Result::ok)
91 .filter(|e| e.path().extension() == Some("json".as_ref()))
92 .map(DirEntry::into_path)
93 .collect()
94 }
95}
96
97fn run_tests(
99 test_files: Vec<PathBuf>,
100 omit_progress: bool,
101 keep_going: bool,
102 print_env_on_error: bool,
103 json_output: bool,
104) -> Result<(), Error> {
105 let mut passed = 0;
106 let mut failed = 0;
107 let mut skipped = 0;
108 let mut failed_paths = Vec::new();
109
110 let start_time = Instant::now();
111 let total_files = test_files.len();
112
113 for (file_index, file_path) in test_files.into_iter().enumerate() {
114 let current_file = file_index + 1;
115 if skip_test(&file_path) {
116 skipped += 1;
117 if json_output {
118 let output = json!({
119 "file": file_path.display().to_string(),
120 "status": "skipped",
121 "reason": "known_issue"
122 });
123 println!("{}", serde_json::to_string(&output).unwrap());
124 } else if !omit_progress {
125 println!(
126 "Skipping ({}/{}): {}",
127 current_file,
128 total_files,
129 file_path.display()
130 );
131 }
132 continue;
133 }
134
135 let result = run_test_file(&file_path, json_output, print_env_on_error);
136
137 match result {
138 Ok(test_count) => {
139 passed += test_count;
140 if json_output {
141 } else if !omit_progress {
143 println!(
144 "ā ({}/{}) {} ({} tests)",
145 current_file,
146 total_files,
147 file_path.display(),
148 test_count
149 );
150 }
151 }
152 Err(e) => {
153 failed += 1;
154 if keep_going {
155 failed_paths.push(file_path.clone());
156 }
157 if json_output {
158 let output = json!({
159 "file": file_path.display().to_string(),
160 "error": e.to_string(),
161 "status": "failed"
162 });
163 println!("{}", serde_json::to_string(&output).unwrap());
164 } else if !omit_progress {
165 eprintln!(
166 "ā ({}/{}) {} - {}",
167 current_file,
168 total_files,
169 file_path.display(),
170 e
171 );
172 }
173
174 if !keep_going {
175 return Err(e);
176 }
177 }
178 }
179 }
180
181 let duration = start_time.elapsed();
182
183 if json_output {
184 let results = json!({
185 "summary": {
186 "passed": passed,
187 "failed": failed,
188 "skipped": skipped,
189 "duration_secs": duration.as_secs_f64(),
190 }
191 });
192 println!("{}", serde_json::to_string(&results).unwrap());
193 } else {
194 if keep_going && !failed_paths.is_empty() {
196 println!("\nFailed test files:");
197 for path in &failed_paths {
198 println!(" {}", path.display());
199 }
200 }
201
202 println!("\nTest results:");
203 println!(" Passed: {passed}");
204 println!(" Failed: {failed}");
205 println!(" Skipped: {skipped}");
206 println!(" Time: {:.2}s", duration.as_secs_f64());
207 }
208
209 if failed > 0 {
210 Err(Error::TestsFailed { failed })
211 } else {
212 Ok(())
213 }
214}
215
216fn run_test_file(
218 file_path: &Path,
219 json_output: bool,
220 print_env_on_error: bool,
221) -> Result<usize, Error> {
222 let content =
223 fs::read_to_string(file_path).map_err(|e| Error::FileRead(file_path.to_path_buf(), e))?;
224
225 let blockchain_test: BlockchainTest = serde_json::from_str(&content)
226 .map_err(|e| Error::JsonDecode(file_path.to_path_buf(), e))?;
227
228 let mut test_count = 0;
229
230 for (test_name, test_case) in blockchain_test.0 {
231 if json_output {
232 let output = json!({
234 "test": test_name,
235 "file": file_path.display().to_string(),
236 "status": "running"
237 });
238 println!("{}", serde_json::to_string(&output).unwrap());
239 } else {
240 println!(" Running: {test_name}");
241 }
242 let result = execute_blockchain_test(&test_case, print_env_on_error, json_output);
244
245 match result {
246 Ok(()) => {
247 if json_output {
248 let output = json!({
249 "test": test_name,
250 "file": file_path.display().to_string(),
251 "status": "passed"
252 });
253 println!("{}", serde_json::to_string(&output).unwrap());
254 }
255 test_count += 1;
256 }
257 Err(e) => {
258 if json_output {
259 let output = json!({
260 "test": test_name,
261 "file": file_path.display().to_string(),
262 "status": "failed",
263 "error": e.to_string()
264 });
265 println!("{}", serde_json::to_string(&output).unwrap());
266 }
267 return Err(Error::TestExecution {
268 test_name: test_name.clone(),
269 test_path: file_path.to_path_buf(),
270 error: e.to_string(),
271 });
272 }
273 }
274 }
275
276 Ok(test_count)
277}
278
279#[derive(Debug, Clone)]
281struct DebugInfo {
282 pre_state: HashMap<Address, (AccountInfo, HashMap<U256, U256>)>,
284 tx_env: Option<revm::context::tx::TxEnv>,
286 block_env: revm::context::block::BlockEnv,
288 cfg_env: CfgEnv,
290 block_idx: usize,
292 tx_idx: usize,
294 withdrawals: Option<Vec<Withdrawal>>,
296}
297
298impl DebugInfo {
299 fn capture_committed_state(
301 state: &State<EmptyDB>,
302 ) -> HashMap<Address, (AccountInfo, HashMap<U256, U256>)> {
303 let mut committed_state = HashMap::new();
304
305 for (address, cache_account) in &state.cache.accounts {
307 if let Some(plain_account) = &cache_account.account {
308 let mut storage = HashMap::new();
309 for (key, value) in &plain_account.storage {
310 storage.insert(*key, *value);
311 }
312 committed_state.insert(*address, (plain_account.info.clone(), storage));
313 }
314 }
315
316 committed_state
317 }
318}
319
320fn validate_post_state(
322 state: &mut State<EmptyDB>,
323 expected_post_state: &BTreeMap<Address, Account>,
324 debug_info: &DebugInfo,
325 print_env_on_error: bool,
326) -> Result<(), TestExecutionError> {
327 for (address, expected_account) in expected_post_state {
328 let actual_account = state
330 .load_cache_account(*address)
331 .map_err(|e| TestExecutionError::Database(format!("Account load failed: {e}")))?;
332 let info = actual_account
333 .account
334 .as_ref()
335 .map(|a| a.info.clone())
336 .unwrap_or_default();
337
338 if info.balance != expected_account.balance {
340 if print_env_on_error {
341 print_error_with_state(debug_info, state, Some(expected_post_state));
342 }
343 return Err(TestExecutionError::PostStateValidation {
344 address: *address,
345 field: "balance".to_string(),
346 expected: format!("{}", expected_account.balance),
347 actual: format!("{}", info.balance),
348 });
349 }
350
351 let expected_nonce = expected_account.nonce.to::<u64>();
353 if info.nonce != expected_nonce {
354 if print_env_on_error {
355 print_error_with_state(debug_info, state, Some(expected_post_state));
356 }
357 return Err(TestExecutionError::PostStateValidation {
358 address: *address,
359 field: "nonce".to_string(),
360 expected: format!("{expected_nonce}"),
361 actual: format!("{}", info.nonce),
362 });
363 }
364
365 if !expected_account.code.is_empty() {
367 if let Some(actual_code) = &info.code {
368 if actual_code.original_bytes() != expected_account.code {
369 if print_env_on_error {
370 print_error_with_state(debug_info, state, Some(expected_post_state));
371 }
372 return Err(TestExecutionError::PostStateValidation {
373 address: *address,
374 field: "code".to_string(),
375 expected: format!("0x{}", hex::encode(&expected_account.code)),
376 actual: format!("0x{}", hex::encode(actual_code.bytecode())),
377 });
378 }
379 } else {
380 if print_env_on_error {
381 print_error_with_state(debug_info, state, Some(expected_post_state));
382 }
383 return Err(TestExecutionError::PostStateValidation {
384 address: *address,
385 field: "code".to_string(),
386 expected: format!("0x{}", hex::encode(&expected_account.code)),
387 actual: "empty".to_string(),
388 });
389 }
390 }
391
392 for (slot, actual_value) in actual_account
394 .account
395 .as_ref()
396 .map(|a| &a.storage)
397 .unwrap_or(&HashMap::new())
398 .iter()
399 {
400 let slot = *slot;
401 let actual_value = *actual_value;
402 if !expected_account.storage.contains_key(&slot) && !actual_value.is_zero() {
403 if print_env_on_error {
404 print_error_with_state(debug_info, state, Some(expected_post_state));
405 }
406 return Err(TestExecutionError::PostStateValidation {
407 address: *address,
408 field: format!("storage_unexpected[{slot}]"),
409 expected: "0x0".to_string(),
410 actual: format!("{actual_value}"),
411 });
412 }
413 }
414
415 for (slot, expected_value) in &expected_account.storage {
417 let actual_value = state.storage(*address, *slot);
418 let actual_value = actual_value.unwrap_or_default();
419
420 if actual_value != *expected_value {
421 if print_env_on_error {
422 print_error_with_state(debug_info, state, Some(expected_post_state));
423 }
424
425 return Err(TestExecutionError::PostStateValidation {
426 address: *address,
427 field: format!("storage_validation[{slot}]"),
428 expected: format!("{expected_value}"),
429 actual: format!("{actual_value}"),
430 });
431 }
432 }
433 }
434 Ok(())
435}
436
437fn print_error_with_state(
439 debug_info: &DebugInfo,
440 current_state: &State<EmptyDB>,
441 expected_post_state: Option<&BTreeMap<Address, Account>>,
442) {
443 eprintln!("\n========== TEST EXECUTION ERROR ==========");
444
445 eprintln!(
447 "\nš Error occurred at block {} transaction {}",
448 debug_info.block_idx, debug_info.tx_idx
449 );
450
451 eprintln!("\nš Configuration Environment:");
453 eprintln!(" Spec ID: {:?}", debug_info.cfg_env.spec);
454 eprintln!(" Chain ID: {}", debug_info.cfg_env.chain_id);
455 eprintln!(
456 " Limit contract code size: {:?}",
457 debug_info.cfg_env.limit_contract_code_size
458 );
459 eprintln!(
460 " Limit contract initcode size: {:?}",
461 debug_info.cfg_env.limit_contract_initcode_size
462 );
463
464 eprintln!("\nšØ Block Environment:");
466 eprintln!(" Number: {}", debug_info.block_env.number);
467 eprintln!(" Timestamp: {}", debug_info.block_env.timestamp);
468 eprintln!(" Gas limit: {}", debug_info.block_env.gas_limit);
469 eprintln!(" Base fee: {:?}", debug_info.block_env.basefee);
470 eprintln!(" Difficulty: {}", debug_info.block_env.difficulty);
471 eprintln!(" Prevrandao: {:?}", debug_info.block_env.prevrandao);
472 eprintln!(" Beneficiary: {:?}", debug_info.block_env.beneficiary);
473 eprintln!(
474 " Blob excess gas: {:?}",
475 debug_info.block_env.blob_excess_gas_and_price
476 );
477
478 if let Some(withdrawals) = &debug_info.withdrawals {
480 eprintln!(" Withdrawals: {} items", withdrawals.len());
481 if !withdrawals.is_empty() {
482 for (i, withdrawal) in withdrawals.iter().enumerate().take(3) {
483 eprintln!(" Withdrawal {i}:");
484 eprintln!(" Index: {}", withdrawal.index);
485 eprintln!(" Validator Index: {}", withdrawal.validator_index);
486 eprintln!(" Address: {:?}", withdrawal.address);
487 eprintln!(
488 " Amount: {} Gwei ({:.6} ETH)",
489 withdrawal.amount,
490 withdrawal.amount.to::<u128>() as f64 / 1_000_000_000.0
491 );
492 }
493 if withdrawals.len() > 3 {
494 eprintln!(" ... and {} more withdrawals", withdrawals.len() - 3);
495 }
496 }
497 }
498
499 if let Some(tx_env) = &debug_info.tx_env {
501 eprintln!("\nš Transaction Environment:");
502 eprintln!(" Transaction type: {}", tx_env.tx_type);
503 eprintln!(" Caller: {:?}", tx_env.caller);
504 eprintln!(" Gas limit: {}", tx_env.gas_limit);
505 eprintln!(" Gas price: {}", tx_env.gas_price);
506 eprintln!(" Gas priority fee: {:?}", tx_env.gas_priority_fee);
507 eprintln!(" Transaction kind: {:?}", tx_env.kind);
508 eprintln!(" Value: {}", tx_env.value);
509 eprintln!(" Data length: {} bytes", tx_env.data.len());
510 if !tx_env.data.is_empty() {
511 let preview_len = std::cmp::min(64, tx_env.data.len());
512 eprintln!(
513 " Data preview: 0x{}{}",
514 hex::encode(&tx_env.data[..preview_len]),
515 if tx_env.data.len() > 64 { "..." } else { "" }
516 );
517 }
518 eprintln!(" Nonce: {}", tx_env.nonce);
519 eprintln!(" Chain ID: {:?}", tx_env.chain_id);
520 eprintln!(" Access list: {} entries", tx_env.access_list.len());
521 if !tx_env.access_list.is_empty() {
522 for (i, access) in tx_env.access_list.iter().enumerate().take(3) {
523 eprintln!(
524 " Access {}: address={:?}, {} storage keys",
525 i,
526 access.address,
527 access.storage_keys.len()
528 );
529 }
530 if tx_env.access_list.len() > 3 {
531 eprintln!(
532 " ... and {} more access list entries",
533 tx_env.access_list.len() - 3
534 );
535 }
536 }
537 eprintln!(" Blob hashes: {} blobs", tx_env.blob_hashes.len());
538 if !tx_env.blob_hashes.is_empty() {
539 for (i, hash) in tx_env.blob_hashes.iter().enumerate().take(3) {
540 eprintln!(" Blob {i}: {hash:?}");
541 }
542 if tx_env.blob_hashes.len() > 3 {
543 eprintln!(
544 " ... and {} more blob hashes",
545 tx_env.blob_hashes.len() - 3
546 );
547 }
548 }
549 eprintln!(" Max fee per blob gas: {}", tx_env.max_fee_per_blob_gas);
550 eprintln!(
551 " Authorization list: {} items",
552 tx_env.authorization_list.len()
553 );
554 if !tx_env.authorization_list.is_empty() {
555 eprintln!(" (EIP-7702 authorizations present)");
556 }
557 } else {
558 eprintln!(
559 "\nš Transaction Environment: Not available (error occurred before tx creation)"
560 );
561 }
562
563 eprintln!("\nš¾ Pre-State (Initial):");
565 for (address, (info, storage)) in &debug_info.pre_state {
566 eprintln!(" Account {address:?}:");
567 eprintln!(" Balance: 0x{:x}", info.balance);
568 eprintln!(" Nonce: {}", info.nonce);
569 eprintln!(" Code hash: {:?}", info.code_hash);
570 eprintln!(
571 " Code size: {} bytes",
572 info.code.as_ref().map_or(0, |c| c.bytecode().len())
573 );
574 if !storage.is_empty() {
575 eprintln!(" Storage ({} slots):", storage.len());
576 for (key, value) in storage.iter().take(5) {
577 eprintln!(" {key:?} => {value:?}");
578 }
579 if storage.len() > 5 {
580 eprintln!(" ... and {} more slots", storage.len() - 5);
581 }
582 }
583 }
584
585 eprintln!("\nš Current State (Actual):");
586 let committed_state = DebugInfo::capture_committed_state(current_state);
587 for (address, (info, storage)) in &committed_state {
588 eprintln!(" Account {address:?}:");
589 eprintln!(" Balance: 0x{:x}", info.balance);
590 eprintln!(" Nonce: {}", info.nonce);
591 eprintln!(" Code hash: {:?}", info.code_hash);
592 eprintln!(
593 " Code size: {} bytes",
594 info.code.as_ref().map_or(0, |c| c.bytecode().len())
595 );
596 if !storage.is_empty() {
597 eprintln!(" Storage ({} slots):", storage.len());
598 for (key, value) in storage.iter().take(5) {
599 eprintln!(" {key:?} => {value:?}");
600 }
601 if storage.len() > 5 {
602 eprintln!(" ... and {} more slots", storage.len() - 5);
603 }
604 }
605 }
606
607 if let Some(expected_post_state) = expected_post_state {
609 eprintln!("\nā
Expected Post-State:");
610 for (address, account) in expected_post_state {
611 eprintln!(" Account {address:?}:");
612 eprintln!(" Balance: 0x{:x}", account.balance);
613 eprintln!(" Nonce: {}", account.nonce);
614 if !account.code.is_empty() {
615 eprintln!(" Code size: {} bytes", account.code.len());
616 }
617 if !account.storage.is_empty() {
618 eprintln!(" Storage ({} slots):", account.storage.len());
619 for (key, value) in account.storage.iter().take(5) {
620 eprintln!(" {key:?} => {value:?}");
621 }
622 if account.storage.len() > 5 {
623 eprintln!(" ... and {} more slots", account.storage.len() - 5);
624 }
625 }
626 }
627 }
628
629 eprintln!("\n===========================================\n");
630}
631
632fn execute_blockchain_test(
634 test_case: &BlockchainTestCase,
635 print_env_on_error: bool,
636 json_output: bool,
637) -> Result<(), TestExecutionError> {
638 if matches!(
640 test_case.network,
641 ForkSpec::ByzantiumToConstantinopleAt5
642 | ForkSpec::ParisToShanghaiAtTime15k
643 | ForkSpec::ShanghaiToCancunAtTime15k
644 | ForkSpec::CancunToPragueAtTime15k
645 | ForkSpec::PragueToOsakaAtTime15k
646 | ForkSpec::BPO1ToBPO2AtTime15k
647 ) {
648 eprintln!("ā ļø Skipping transition fork: {:?}", test_case.network);
649 return Ok(());
650 }
651
652 let mut state = State::builder().build();
654
655 let mut pre_state_debug = HashMap::new();
657
658 let genesis_state = test_case.pre.clone().into_genesis_state();
660 for (address, account) in genesis_state {
661 let account_info = AccountInfo {
662 balance: account.balance,
663 nonce: account.nonce,
664 code_hash: primitives::keccak256(&account.code),
665 code: Some(bytecode::Bytecode::new_raw(account.code.clone())),
666 };
667
668 if print_env_on_error {
670 pre_state_debug.insert(address, (account_info.clone(), account.storage.clone()));
671 }
672
673 state.insert_account_with_storage(address, account_info, account.storage);
674 }
675
676 let spec_id = fork_to_spec_id(test_case.network);
678 let mut cfg = CfgEnv::default();
679 cfg.spec = spec_id;
680
681 let mut parent_block_hash = Some(test_case.genesis_block_header.hash);
683 let mut parent_excess_blob_gas = test_case
684 .genesis_block_header
685 .excess_blob_gas
686 .unwrap_or_default()
687 .to::<u64>();
688 let mut block_env = test_case.genesis_block_env();
689
690 for (block_idx, block) in test_case.blocks.iter().enumerate() {
692 println!("Run block {block_idx}/{}", test_case.blocks.len());
693
694 let should_fail = block.expect_exception.is_some();
696
697 let transactions = block.transactions.as_deref().unwrap_or_default();
698
699 let mut block_hash = None;
702 let mut beacon_root = None;
703 let this_excess_blob_gas;
704
705 if let Some(block_header) = block.block_header.as_ref() {
706 block_hash = Some(block_header.hash);
707 beacon_root = block_header.parent_beacon_block_root;
708 block_env = block_header.to_block_env(Some(BlobExcessGasAndPrice::new_with_spec(
709 parent_excess_blob_gas,
710 spec_id,
711 )));
712 this_excess_blob_gas = block_header.excess_blob_gas.map(|i| i.to::<u64>());
713 } else {
714 this_excess_blob_gas = None;
715 }
716
717 let evm_context = Context::mainnet()
719 .with_block(&block_env)
720 .with_cfg(&cfg)
721 .with_db(&mut state);
722
723 let mut evm = evm_context.build_mainnet_with_inspector(TracerEip3155::new_stdout());
725
726 pre_block::pre_block_transition(&mut evm, spec_id, parent_block_hash, beacon_root);
728
729 for (tx_idx, tx) in transactions.iter().enumerate() {
731 if tx.sender.is_none() {
732 if print_env_on_error {
733 let debug_info = DebugInfo {
734 pre_state: pre_state_debug.clone(),
735 tx_env: None,
736 block_env: block_env.clone(),
737 cfg_env: cfg.clone(),
738 block_idx,
739 tx_idx,
740 withdrawals: block.withdrawals.clone(),
741 };
742 print_error_with_state(
743 &debug_info,
744 evm.ctx().db_ref(),
745 test_case.post_state.as_ref(),
746 );
747 }
748 if json_output {
749 let output = json!({
750 "block": block_idx,
751 "tx": tx_idx,
752 "error": "missing sender",
753 "status": "skipped"
754 });
755 println!("{}", serde_json::to_string(&output).unwrap());
756 } else {
757 eprintln!("ā ļø Skipping block {block_idx} due to missing sender");
758 }
759 break; }
761
762 let tx_env = match tx.to_tx_env() {
763 Ok(env) => env,
764 Err(e) => {
765 if should_fail {
766 continue;
768 }
769 if print_env_on_error {
770 let debug_info = DebugInfo {
771 pre_state: pre_state_debug.clone(),
772 tx_env: None,
773 block_env: block_env.clone(),
774 cfg_env: cfg.clone(),
775 block_idx,
776 tx_idx,
777 withdrawals: block.withdrawals.clone(),
778 };
779 print_error_with_state(
780 &debug_info,
781 evm.ctx().db_ref(),
782 test_case.post_state.as_ref(),
783 );
784 }
785 if json_output {
786 let output = json!({
787 "block": block_idx,
788 "tx": tx_idx,
789 "error": format!("tx env creation error: {e}"),
790 "status": "skipped"
791 });
792 println!("{}", serde_json::to_string(&output).unwrap());
793 } else {
794 eprintln!(
795 "ā ļø Skipping block {block_idx} due to transaction env creation error: {e}"
796 );
797 }
798 break; }
800 };
801
802 let execution_result = if json_output {
804 evm.inspect_tx(tx_env.clone())
805 } else {
806 evm.transact(tx_env.clone())
807 };
808
809 match execution_result {
810 Ok(result) => {
811 if should_fail {
812 if print_env_on_error {
815 if json_output {
817 eprintln!("=== Transaction trace (unexpected success) ===");
818 }
819 let _ = evm.inspect_tx(tx_env.clone());
820 }
821
822 if print_env_on_error {
823 let debug_info = DebugInfo {
824 pre_state: pre_state_debug.clone(),
825 tx_env: Some(tx_env.clone()),
826 block_env: block_env.clone(),
827 cfg_env: cfg.clone(),
828 block_idx,
829 tx_idx,
830 withdrawals: block.withdrawals.clone(),
831 };
832 print_error_with_state(
833 &debug_info,
834 evm.ctx().db_ref(),
835 test_case.post_state.as_ref(),
836 );
837 }
838 let exception = block.expect_exception.clone().unwrap_or_default();
839 if json_output {
840 let output = json!({
841 "block": block_idx,
842 "tx": tx_idx,
843 "error": format!("expected failure: {exception}"),
844 "gas_used": result.result.gas_used(),
845 "status": "unexpected_success"
846 });
847 println!("{}", serde_json::to_string(&output).unwrap());
848 } else {
849 eprintln!(
850 "ā ļø Skipping block {block_idx} due to expected failure: {exception}"
851 );
852 }
853 break; }
855 evm.commit(result.state);
856 }
857 Err(e) => {
858 if !should_fail {
859 if print_env_on_error {
861 if json_output {
862 eprintln!("=== Transaction trace (unexpected failure) ===");
863 }
864 let _ = evm.inspect_tx(tx_env.clone());
865 }
866
867 if print_env_on_error {
868 let debug_info = DebugInfo {
869 pre_state: pre_state_debug.clone(),
870 tx_env: Some(tx_env.clone()),
871 block_env: block_env.clone(),
872 cfg_env: cfg.clone(),
873 block_idx,
874 tx_idx,
875 withdrawals: block.withdrawals.clone(),
876 };
877 print_error_with_state(
878 &debug_info,
879 evm.ctx().db_ref(),
880 test_case.post_state.as_ref(),
881 );
882 }
883 if json_output {
884 let output = json!({
885 "block": block_idx,
886 "tx": tx_idx,
887 "error": format!("{e:?}"),
888 "status": "unexpected_failure"
889 });
890 println!("{}", serde_json::to_string(&output).unwrap());
891 } else {
892 eprintln!(
893 "ā ļø Skipping block {block_idx} due to unexpected failure: {e:?}"
894 );
895 }
896 break; } else if json_output {
898 let output = json!({
900 "block": block_idx,
901 "tx": tx_idx,
902 "error": format!("{e:?}"),
903 "status": "expected_failure"
904 });
905 println!("{}", serde_json::to_string(&output).unwrap());
906 }
907 }
908 }
909 }
910
911 post_block::post_block_transition(
913 &mut evm,
914 &block_env,
915 block.withdrawals.as_deref().unwrap_or_default(),
916 spec_id,
917 );
918
919 parent_block_hash = block_hash;
920 if let Some(excess_blob_gas) = this_excess_blob_gas {
921 parent_excess_blob_gas = excess_blob_gas;
922 }
923
924 state.merge_transitions(BundleRetention::Reverts);
925 }
926
927 if let Some(expected_post_state) = &test_case.post_state {
929 let debug_info = DebugInfo {
931 pre_state: pre_state_debug.clone(),
932 tx_env: None, block_env: block_env.clone(),
934 cfg_env: cfg.clone(),
935 block_idx: test_case.blocks.len(),
936 tx_idx: 0,
937 withdrawals: test_case.blocks.last().and_then(|b| b.withdrawals.clone()),
938 };
939 validate_post_state(
940 &mut state,
941 expected_post_state,
942 &debug_info,
943 print_env_on_error,
944 )?;
945 }
946
947 Ok(())
948}
949
950fn fork_to_spec_id(fork: ForkSpec) -> SpecId {
952 match fork {
953 ForkSpec::Frontier => SpecId::FRONTIER,
954 ForkSpec::Homestead | ForkSpec::FrontierToHomesteadAt5 => SpecId::HOMESTEAD,
955 ForkSpec::EIP150 | ForkSpec::HomesteadToDaoAt5 | ForkSpec::HomesteadToEIP150At5 => {
956 SpecId::TANGERINE
957 }
958 ForkSpec::EIP158 => SpecId::SPURIOUS_DRAGON,
959 ForkSpec::Byzantium
960 | ForkSpec::EIP158ToByzantiumAt5
961 | ForkSpec::ByzantiumToConstantinopleFixAt5 => SpecId::BYZANTIUM,
962 ForkSpec::Constantinople | ForkSpec::ByzantiumToConstantinopleAt5 => SpecId::PETERSBURG,
963 ForkSpec::ConstantinopleFix => SpecId::PETERSBURG,
964 ForkSpec::Istanbul => SpecId::ISTANBUL,
965 ForkSpec::Berlin => SpecId::BERLIN,
966 ForkSpec::London | ForkSpec::BerlinToLondonAt5 => SpecId::LONDON,
967 ForkSpec::Paris | ForkSpec::ParisToShanghaiAtTime15k => SpecId::MERGE,
968 ForkSpec::Shanghai => SpecId::SHANGHAI,
969 ForkSpec::Cancun | ForkSpec::ShanghaiToCancunAtTime15k => SpecId::CANCUN,
970 ForkSpec::Prague | ForkSpec::CancunToPragueAtTime15k => SpecId::PRAGUE,
971 ForkSpec::Osaka | ForkSpec::PragueToOsakaAtTime15k => SpecId::OSAKA,
972 _ => SpecId::OSAKA, }
974}
975
976fn skip_test(path: &Path) -> bool {
978 let name = path.file_name().unwrap().to_str().unwrap();
979
980 matches!(
982 name,
983 "CreateTransactionHighNonce.json"
985
986 | "RevertInCreateInInit_Paris.json"
988 | "RevertInCreateInInit.json"
989 | "dynamicAccountOverwriteEmpty.json"
990 | "dynamicAccountOverwriteEmpty_Paris.json"
991 | "RevertInCreateInInitCreate2Paris.json"
992 | "create2collisionStorage.json"
993 | "RevertInCreateInInitCreate2.json"
994 | "create2collisionStorageParis.json"
995 | "InitCollision.json"
996 | "InitCollisionParis.json"
997
998 | "ValueOverflow.json"
1000 | "ValueOverflowParis.json"
1001
1002 | "Call50000_sha256.json"
1004 | "static_Call50000_sha256.json"
1005 | "loopMul.json"
1006 | "CALLBlake2f_MaxRounds.json"
1007 | "scenarios.json"
1009 | "invalid_tx_max_fee_per_blob_gas.json"
1011 | "correct_increasing_blob_gas_costs.json"
1012 | "correct_decreasing_blob_gas_costs.json"
1013
1014 | "invalid_negative_excess_blob_gas.json"
1016 | "invalid_excess_blob_gas_change.json"
1018 | "invalid_static_excess_blob_gas.json"
1020 | "invalid_excess_blob_gas_target_blobs_increase_from_zero.json"
1022 | "invalid_zero_excess_blob_gas_in_header.json"
1024 | "invalid_excess_blob_gas_above_target_change.json"
1026 | "invalid_non_multiple_excess_blob_gas.json"
1028 | "invalid_static_excess_blob_gas_from_zero_on_blobs_above_target.json"
1030 | "system_contract_errors.json"
1032 | "consolidation_requests.json"
1034 | "block_hashes_history.json"
1036 | "valid_multi_type_request_from_same_tx.json"
1038 | "valid_multi_type_requests.json"
1040 | "withdrawal_requests.json"
1044 )
1045}
1046
1047#[derive(Debug, Error)]
1048pub enum TestExecutionError {
1049 #[error("Database error: {0}")]
1050 Database(String),
1051
1052 #[error("Skipped fork: {0}")]
1053 SkippedFork(String),
1054
1055 #[error("Sender is required")]
1056 SenderRequired,
1057
1058 #[error("Expected failure at block {block_idx}, tx {tx_idx}: {message}")]
1059 ExpectedFailure {
1060 block_idx: usize,
1061 tx_idx: usize,
1062 message: String,
1063 },
1064
1065 #[error("Unexpected failure at block {block_idx}, tx {tx_idx}: {error}")]
1066 UnexpectedFailure {
1067 block_idx: usize,
1068 tx_idx: usize,
1069 error: String,
1070 },
1071
1072 #[error("Transaction env creation failed at block {block_idx}, tx {tx_idx}: {error}")]
1073 TransactionEnvCreation {
1074 block_idx: usize,
1075 tx_idx: usize,
1076 error: String,
1077 },
1078
1079 #[error("Unexpected revert at block {block_idx}, tx {tx_idx}, gas used: {gas_used}")]
1080 UnexpectedRevert {
1081 block_idx: usize,
1082 tx_idx: usize,
1083 gas_used: u64,
1084 },
1085
1086 #[error("Unexpected halt at block {block_idx}, tx {tx_idx}: {reason:?}, gas used: {gas_used}")]
1087 UnexpectedHalt {
1088 block_idx: usize,
1089 tx_idx: usize,
1090 reason: HaltReason,
1091 gas_used: u64,
1092 },
1093
1094 #[error(
1095 "Post-state validation failed for {address:?}.{field}: expected {expected}, got {actual}"
1096 )]
1097 PostStateValidation {
1098 address: Address,
1099 field: String,
1100 expected: String,
1101 actual: String,
1102 },
1103}
1104
1105#[derive(Debug, Error)]
1106pub enum Error {
1107 #[error("Path not found: {0}")]
1108 PathNotFound(PathBuf),
1109
1110 #[error("No JSON files found in: {0}")]
1111 NoJsonFiles(PathBuf),
1112
1113 #[error("Failed to read file {0}: {1}")]
1114 FileRead(PathBuf, std::io::Error),
1115
1116 #[error("Failed to decode JSON from {0}: {1}")]
1117 JsonDecode(PathBuf, serde_json::Error),
1118
1119 #[error("Test execution failed for {test_name} in {test_path}: {error}")]
1120 TestExecution {
1121 test_name: String,
1122 test_path: PathBuf,
1123 error: String,
1124 },
1125
1126 #[error("Directory traversal error: {0}")]
1127 WalkDir(#[from] walkdir::Error),
1128
1129 #[error("{failed} tests failed")]
1130 TestsFailed { failed: usize },
1131}