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 state
678 .block_hashes
679 .insert(0, test_case.genesis_block_header.hash);
680
681 let spec_id = fork_to_spec_id(test_case.network);
683 let mut cfg = CfgEnv::default();
684 cfg.spec = spec_id;
685
686 let mut parent_block_hash = Some(test_case.genesis_block_header.hash);
688 let mut parent_excess_blob_gas = test_case
689 .genesis_block_header
690 .excess_blob_gas
691 .unwrap_or_default()
692 .to::<u64>();
693 let mut block_env = test_case.genesis_block_env();
694
695 for (block_idx, block) in test_case.blocks.iter().enumerate() {
697 println!("Run block {block_idx}/{}", test_case.blocks.len());
698
699 let should_fail = block.expect_exception.is_some();
701
702 let transactions = block.transactions.as_deref().unwrap_or_default();
703
704 let mut block_hash = None;
707 let mut beacon_root = None;
708 let this_excess_blob_gas;
709
710 if let Some(block_header) = block.block_header.as_ref() {
711 block_hash = Some(block_header.hash);
712 beacon_root = block_header.parent_beacon_block_root;
713 block_env = block_header.to_block_env(Some(BlobExcessGasAndPrice::new_with_spec(
714 parent_excess_blob_gas,
715 spec_id,
716 )));
717 this_excess_blob_gas = block_header.excess_blob_gas.map(|i| i.to::<u64>());
718 } else {
719 this_excess_blob_gas = None;
720 }
721
722 let evm_context = Context::mainnet()
724 .with_block(&block_env)
725 .with_cfg(&cfg)
726 .with_db(&mut state);
727
728 let mut evm = evm_context.build_mainnet_with_inspector(TracerEip3155::new_stdout());
730
731 pre_block::pre_block_transition(&mut evm, spec_id, parent_block_hash, beacon_root);
733
734 for (tx_idx, tx) in transactions.iter().enumerate() {
736 if tx.sender.is_none() {
737 if print_env_on_error {
738 let debug_info = DebugInfo {
739 pre_state: pre_state_debug.clone(),
740 tx_env: None,
741 block_env: block_env.clone(),
742 cfg_env: cfg.clone(),
743 block_idx,
744 tx_idx,
745 withdrawals: block.withdrawals.clone(),
746 };
747 print_error_with_state(
748 &debug_info,
749 evm.ctx().db_ref(),
750 test_case.post_state.as_ref(),
751 );
752 }
753 if json_output {
754 let output = json!({
755 "block": block_idx,
756 "tx": tx_idx,
757 "error": "missing sender",
758 "status": "skipped"
759 });
760 println!("{}", serde_json::to_string(&output).unwrap());
761 } else {
762 eprintln!("ā ļø Skipping block {block_idx} due to missing sender");
763 }
764 break; }
766
767 let tx_env = match tx.to_tx_env() {
768 Ok(env) => env,
769 Err(e) => {
770 if should_fail {
771 continue;
773 }
774 if print_env_on_error {
775 let debug_info = DebugInfo {
776 pre_state: pre_state_debug.clone(),
777 tx_env: None,
778 block_env: block_env.clone(),
779 cfg_env: cfg.clone(),
780 block_idx,
781 tx_idx,
782 withdrawals: block.withdrawals.clone(),
783 };
784 print_error_with_state(
785 &debug_info,
786 evm.ctx().db_ref(),
787 test_case.post_state.as_ref(),
788 );
789 }
790 if json_output {
791 let output = json!({
792 "block": block_idx,
793 "tx": tx_idx,
794 "error": format!("tx env creation error: {e}"),
795 "status": "skipped"
796 });
797 println!("{}", serde_json::to_string(&output).unwrap());
798 } else {
799 eprintln!(
800 "ā ļø Skipping block {block_idx} due to transaction env creation error: {e}"
801 );
802 }
803 break; }
805 };
806
807 let execution_result = if json_output {
809 evm.inspect_tx(tx_env.clone())
810 } else {
811 evm.transact(tx_env.clone())
812 };
813
814 match execution_result {
815 Ok(result) => {
816 if should_fail {
817 if print_env_on_error {
820 if json_output {
822 eprintln!("=== Transaction trace (unexpected success) ===");
823 }
824 let _ = evm.inspect_tx(tx_env.clone());
825 }
826
827 if print_env_on_error {
828 let debug_info = DebugInfo {
829 pre_state: pre_state_debug.clone(),
830 tx_env: Some(tx_env.clone()),
831 block_env: block_env.clone(),
832 cfg_env: cfg.clone(),
833 block_idx,
834 tx_idx,
835 withdrawals: block.withdrawals.clone(),
836 };
837 print_error_with_state(
838 &debug_info,
839 evm.ctx().db_ref(),
840 test_case.post_state.as_ref(),
841 );
842 }
843 let exception = block.expect_exception.clone().unwrap_or_default();
844 if json_output {
845 let output = json!({
846 "block": block_idx,
847 "tx": tx_idx,
848 "error": format!("expected failure: {exception}"),
849 "gas_used": result.result.gas_used(),
850 "status": "unexpected_success"
851 });
852 println!("{}", serde_json::to_string(&output).unwrap());
853 } else {
854 eprintln!(
855 "ā ļø Skipping block {block_idx} due to expected failure: {exception}"
856 );
857 }
858 break; }
860 evm.commit(result.state);
861 }
862 Err(e) => {
863 if !should_fail {
864 if print_env_on_error {
866 if json_output {
867 eprintln!("=== Transaction trace (unexpected failure) ===");
868 }
869 let _ = evm.inspect_tx(tx_env.clone());
870 }
871
872 if print_env_on_error {
873 let debug_info = DebugInfo {
874 pre_state: pre_state_debug.clone(),
875 tx_env: Some(tx_env.clone()),
876 block_env: block_env.clone(),
877 cfg_env: cfg.clone(),
878 block_idx,
879 tx_idx,
880 withdrawals: block.withdrawals.clone(),
881 };
882 print_error_with_state(
883 &debug_info,
884 evm.ctx().db_ref(),
885 test_case.post_state.as_ref(),
886 );
887 }
888 if json_output {
889 let output = json!({
890 "block": block_idx,
891 "tx": tx_idx,
892 "error": format!("{e:?}"),
893 "status": "unexpected_failure"
894 });
895 println!("{}", serde_json::to_string(&output).unwrap());
896 } else {
897 eprintln!(
898 "ā ļø Skipping block {block_idx} due to unexpected failure: {e:?}"
899 );
900 }
901 break; } else if json_output {
903 let output = json!({
905 "block": block_idx,
906 "tx": tx_idx,
907 "error": format!("{e:?}"),
908 "status": "expected_failure"
909 });
910 println!("{}", serde_json::to_string(&output).unwrap());
911 }
912 }
913 }
914 }
915
916 post_block::post_block_transition(
918 &mut evm,
919 &block_env,
920 block.withdrawals.as_deref().unwrap_or_default(),
921 spec_id,
922 );
923
924 state
926 .block_hashes
927 .insert(block_env.number.to::<u64>(), block_hash.unwrap_or_default());
928
929 parent_block_hash = block_hash;
930 if let Some(excess_blob_gas) = this_excess_blob_gas {
931 parent_excess_blob_gas = excess_blob_gas;
932 }
933
934 state.merge_transitions(BundleRetention::Reverts);
935 }
936
937 if let Some(expected_post_state) = &test_case.post_state {
939 let debug_info = DebugInfo {
941 pre_state: pre_state_debug.clone(),
942 tx_env: None, block_env: block_env.clone(),
944 cfg_env: cfg.clone(),
945 block_idx: test_case.blocks.len(),
946 tx_idx: 0,
947 withdrawals: test_case.blocks.last().and_then(|b| b.withdrawals.clone()),
948 };
949 validate_post_state(
950 &mut state,
951 expected_post_state,
952 &debug_info,
953 print_env_on_error,
954 )?;
955 }
956
957 Ok(())
958}
959
960fn fork_to_spec_id(fork: ForkSpec) -> SpecId {
962 match fork {
963 ForkSpec::Frontier => SpecId::FRONTIER,
964 ForkSpec::Homestead | ForkSpec::FrontierToHomesteadAt5 => SpecId::HOMESTEAD,
965 ForkSpec::EIP150 | ForkSpec::HomesteadToDaoAt5 | ForkSpec::HomesteadToEIP150At5 => {
966 SpecId::TANGERINE
967 }
968 ForkSpec::EIP158 => SpecId::SPURIOUS_DRAGON,
969 ForkSpec::Byzantium
970 | ForkSpec::EIP158ToByzantiumAt5
971 | ForkSpec::ByzantiumToConstantinopleFixAt5 => SpecId::BYZANTIUM,
972 ForkSpec::Constantinople | ForkSpec::ByzantiumToConstantinopleAt5 => SpecId::PETERSBURG,
973 ForkSpec::ConstantinopleFix => SpecId::PETERSBURG,
974 ForkSpec::Istanbul => SpecId::ISTANBUL,
975 ForkSpec::Berlin => SpecId::BERLIN,
976 ForkSpec::London | ForkSpec::BerlinToLondonAt5 => SpecId::LONDON,
977 ForkSpec::Paris | ForkSpec::ParisToShanghaiAtTime15k => SpecId::MERGE,
978 ForkSpec::Shanghai => SpecId::SHANGHAI,
979 ForkSpec::Cancun | ForkSpec::ShanghaiToCancunAtTime15k => SpecId::CANCUN,
980 ForkSpec::Prague | ForkSpec::CancunToPragueAtTime15k => SpecId::PRAGUE,
981 ForkSpec::Osaka | ForkSpec::PragueToOsakaAtTime15k => SpecId::OSAKA,
982 _ => SpecId::OSAKA, }
984}
985
986fn skip_test(path: &Path) -> bool {
988 let name = path.file_name().unwrap().to_str().unwrap();
989
990 matches!(
992 name,
993 "CreateTransactionHighNonce.json"
995
996 | "RevertInCreateInInit_Paris.json"
998 | "RevertInCreateInInit.json"
999 | "dynamicAccountOverwriteEmpty.json"
1000 | "dynamicAccountOverwriteEmpty_Paris.json"
1001 | "RevertInCreateInInitCreate2Paris.json"
1002 | "create2collisionStorage.json"
1003 | "RevertInCreateInInitCreate2.json"
1004 | "create2collisionStorageParis.json"
1005 | "InitCollision.json"
1006 | "InitCollisionParis.json"
1007
1008 | "ValueOverflow.json"
1010 | "ValueOverflowParis.json"
1011
1012 | "Call50000_sha256.json"
1014 | "static_Call50000_sha256.json"
1015 | "loopMul.json"
1016 | "CALLBlake2f_MaxRounds.json"
1017 | "scenarios.json"
1019 | "invalid_tx_max_fee_per_blob_gas.json"
1021 | "correct_increasing_blob_gas_costs.json"
1022 | "correct_decreasing_blob_gas_costs.json"
1023
1024 | "invalid_negative_excess_blob_gas.json"
1026 | "invalid_excess_blob_gas_change.json"
1028 | "invalid_static_excess_blob_gas.json"
1030 | "invalid_excess_blob_gas_target_blobs_increase_from_zero.json"
1032 | "invalid_zero_excess_blob_gas_in_header.json"
1034 | "invalid_excess_blob_gas_above_target_change.json"
1036 | "invalid_non_multiple_excess_blob_gas.json"
1038 | "invalid_static_excess_blob_gas_from_zero_on_blobs_above_target.json"
1040 | "system_contract_errors.json"
1042 | "consolidation_requests.json"
1044 | "block_hashes_history.json"
1046 | "valid_multi_type_request_from_same_tx.json"
1048 | "valid_multi_type_requests.json"
1050 | "withdrawal_requests.json"
1054 )
1055}
1056
1057#[derive(Debug, Error)]
1058pub enum TestExecutionError {
1059 #[error("Database error: {0}")]
1060 Database(String),
1061
1062 #[error("Skipped fork: {0}")]
1063 SkippedFork(String),
1064
1065 #[error("Sender is required")]
1066 SenderRequired,
1067
1068 #[error("Expected failure at block {block_idx}, tx {tx_idx}: {message}")]
1069 ExpectedFailure {
1070 block_idx: usize,
1071 tx_idx: usize,
1072 message: String,
1073 },
1074
1075 #[error("Unexpected failure at block {block_idx}, tx {tx_idx}: {error}")]
1076 UnexpectedFailure {
1077 block_idx: usize,
1078 tx_idx: usize,
1079 error: String,
1080 },
1081
1082 #[error("Transaction env creation failed at block {block_idx}, tx {tx_idx}: {error}")]
1083 TransactionEnvCreation {
1084 block_idx: usize,
1085 tx_idx: usize,
1086 error: String,
1087 },
1088
1089 #[error("Unexpected revert at block {block_idx}, tx {tx_idx}, gas used: {gas_used}")]
1090 UnexpectedRevert {
1091 block_idx: usize,
1092 tx_idx: usize,
1093 gas_used: u64,
1094 },
1095
1096 #[error("Unexpected halt at block {block_idx}, tx {tx_idx}: {reason:?}, gas used: {gas_used}")]
1097 UnexpectedHalt {
1098 block_idx: usize,
1099 tx_idx: usize,
1100 reason: HaltReason,
1101 gas_used: u64,
1102 },
1103
1104 #[error(
1105 "Post-state validation failed for {address:?}.{field}: expected {expected}, got {actual}"
1106 )]
1107 PostStateValidation {
1108 address: Address,
1109 field: String,
1110 expected: String,
1111 actual: String,
1112 },
1113}
1114
1115#[derive(Debug, Error)]
1116pub enum Error {
1117 #[error("Path not found: {0}")]
1118 PathNotFound(PathBuf),
1119
1120 #[error("No JSON files found in: {0}")]
1121 NoJsonFiles(PathBuf),
1122
1123 #[error("Failed to read file {0}: {1}")]
1124 FileRead(PathBuf, std::io::Error),
1125
1126 #[error("Failed to decode JSON from {0}: {1}")]
1127 JsonDecode(PathBuf, serde_json::Error),
1128
1129 #[error("Test execution failed for {test_name} in {test_path}: {error}")]
1130 TestExecution {
1131 test_name: String,
1132 test_path: PathBuf,
1133 error: String,
1134 },
1135
1136 #[error("Directory traversal error: {0}")]
1137 WalkDir(#[from] walkdir::Error),
1138
1139 #[error("{failed} tests failed")]
1140 TestsFailed { failed: usize },
1141}