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