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