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, AddressMap, U256Map, 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: AddressMap<(AccountInfo, U256Map<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(state: &State<EmptyDB>) -> AddressMap<(AccountInfo, U256Map<U256>)> {
293 let mut committed_state = AddressMap::default();
294
295 for (address, cache_account) in &state.cache.accounts {
297 if let Some(plain_account) = &cache_account.account {
298 let mut storage = U256Map::default();
299 for (key, value) in &plain_account.storage {
300 storage.insert(*key, *value);
301 }
302 committed_state.insert(*address, (plain_account.info.clone(), storage));
303 }
304 }
305
306 committed_state
307 }
308}
309
310fn validate_post_state(
312 state: &mut State<EmptyDB>,
313 expected_post_state: &BTreeMap<Address, Account>,
314 debug_info: &DebugInfo,
315 print_env_on_error: bool,
316) -> Result<(), TestExecutionError> {
317 #[allow(clippy::too_many_arguments)]
318 fn make_failure(
319 state: &mut State<EmptyDB>,
320 debug_info: &DebugInfo,
321 expected_post_state: &BTreeMap<Address, Account>,
322 print_env_on_error: bool,
323 address: Address,
324 field: String,
325 expected: String,
326 actual: String,
327 ) -> Result<(), TestExecutionError> {
328 if print_env_on_error {
329 print_error_with_state(debug_info, state, Some(expected_post_state));
330 }
331 Err(TestExecutionError::PostStateValidation {
332 address,
333 field,
334 expected,
335 actual,
336 })
337 }
338
339 for (address, expected_account) in expected_post_state {
340 let actual_account = state
342 .load_cache_account(*address)
343 .map_err(|e| TestExecutionError::Database(format!("Account load failed: {e}")))?;
344 let info = actual_account
345 .account
346 .as_ref()
347 .map(|a| a.info.clone())
348 .unwrap_or_default();
349
350 if info.balance != expected_account.balance {
352 return make_failure(
353 state,
354 debug_info,
355 expected_post_state,
356 print_env_on_error,
357 *address,
358 "balance".to_string(),
359 format!("{}", expected_account.balance),
360 format!("{}", info.balance),
361 );
362 }
363
364 let expected_nonce = expected_account.nonce.to::<u64>();
366 if info.nonce != expected_nonce {
367 return make_failure(
368 state,
369 debug_info,
370 expected_post_state,
371 print_env_on_error,
372 *address,
373 "nonce".to_string(),
374 format!("{expected_nonce}"),
375 format!("{}", info.nonce),
376 );
377 }
378
379 if !expected_account.code.is_empty() {
381 if let Some(actual_code) = &info.code {
382 if actual_code.original_bytes() != expected_account.code {
383 return make_failure(
384 state,
385 debug_info,
386 expected_post_state,
387 print_env_on_error,
388 *address,
389 "code".to_string(),
390 format!("0x{}", hex::encode(&expected_account.code)),
391 format!("0x{}", hex::encode(actual_code.original_byte_slice())),
392 );
393 }
394 } else {
395 return make_failure(
396 state,
397 debug_info,
398 expected_post_state,
399 print_env_on_error,
400 *address,
401 "code".to_string(),
402 format!("0x{}", hex::encode(&expected_account.code)),
403 "empty".to_string(),
404 );
405 }
406 }
407
408 if let Some(acc) = actual_account.account.as_ref() {
410 for (slot, actual_value) in &acc.storage {
411 let slot = *slot;
412 let actual_value = *actual_value;
413 if !expected_account.storage.contains_key(&slot) && !actual_value.is_zero() {
414 return make_failure(
415 state,
416 debug_info,
417 expected_post_state,
418 print_env_on_error,
419 *address,
420 format!("storage_unexpected[{slot}]"),
421 "0x0".to_string(),
422 format!("{actual_value}"),
423 );
424 }
425 }
426 }
427
428 for (slot, expected_value) in &expected_account.storage {
430 let actual_value = state.storage(*address, *slot);
431 let actual_value = actual_value.unwrap_or_default();
432
433 if actual_value != *expected_value {
434 return make_failure(
435 state,
436 debug_info,
437 expected_post_state,
438 print_env_on_error,
439 *address,
440 format!("storage_validation[{slot}]"),
441 format!("{expected_value}"),
442 format!("{actual_value}"),
443 );
444 }
445 }
446 }
447 Ok(())
448}
449
450fn print_error_with_state(
452 debug_info: &DebugInfo,
453 current_state: &State<EmptyDB>,
454 expected_post_state: Option<&BTreeMap<Address, Account>>,
455) {
456 eprintln!("\n========== TEST EXECUTION ERROR ==========");
457
458 eprintln!(
460 "\nš Error occurred at block {} transaction {}",
461 debug_info.block_idx, debug_info.tx_idx
462 );
463
464 eprintln!("\nš Configuration Environment:");
466 eprintln!(" Spec ID: {:?}", debug_info.cfg_env.spec());
467 eprintln!(" Chain ID: {}", debug_info.cfg_env.chain_id);
468 eprintln!(
469 " Limit contract code size: {:?}",
470 debug_info.cfg_env.limit_contract_code_size
471 );
472 eprintln!(
473 " Limit contract initcode size: {:?}",
474 debug_info.cfg_env.limit_contract_initcode_size
475 );
476
477 eprintln!("\nšØ Block Environment:");
479 eprintln!(" Number: {}", debug_info.block_env.number);
480 eprintln!(" Timestamp: {}", debug_info.block_env.timestamp);
481 eprintln!(" Gas limit: {}", debug_info.block_env.gas_limit);
482 eprintln!(" Base fee: {:?}", debug_info.block_env.basefee);
483 eprintln!(" Difficulty: {}", debug_info.block_env.difficulty);
484 eprintln!(" Prevrandao: {:?}", debug_info.block_env.prevrandao);
485 eprintln!(" Beneficiary: {:?}", debug_info.block_env.beneficiary);
486 let blob = debug_info.block_env.blob_excess_gas_and_price;
487 eprintln!(" Blob excess gas: {:?}", blob.map(|a| a.excess_blob_gas));
488 eprintln!(" Blob gas price: {:?}", blob.map(|a| a.blob_gasprice));
489
490 if let Some(withdrawals) = &debug_info.withdrawals {
492 eprintln!(" Withdrawals: {} items", withdrawals.len());
493 if !withdrawals.is_empty() {
494 for (i, withdrawal) in withdrawals.iter().enumerate().take(3) {
495 eprintln!(" Withdrawal {i}:");
496 eprintln!(" Index: {}", withdrawal.index);
497 eprintln!(" Validator Index: {}", withdrawal.validator_index);
498 eprintln!(" Address: {:?}", withdrawal.address);
499 eprintln!(
500 " Amount: {} Gwei ({:.6} ETH)",
501 withdrawal.amount,
502 withdrawal.amount.to::<u128>() as f64 / 1_000_000_000.0
503 );
504 }
505 if withdrawals.len() > 3 {
506 eprintln!(" ... and {} more withdrawals", withdrawals.len() - 3);
507 }
508 }
509 }
510
511 if let Some(tx_env) = &debug_info.tx_env {
513 eprintln!("\nš Transaction Environment:");
514 eprintln!(" Transaction type: {}", tx_env.tx_type);
515 eprintln!(" Caller: {:?}", tx_env.caller);
516 eprintln!(" Gas limit: {}", tx_env.gas_limit);
517 eprintln!(" Gas price: {}", tx_env.gas_price);
518 eprintln!(" Gas priority fee: {:?}", tx_env.gas_priority_fee);
519 eprintln!(" Transaction kind: {:?}", tx_env.kind);
520 eprintln!(" Value: {}", tx_env.value);
521 eprintln!(" Data length: {} bytes", tx_env.data.len());
522 if !tx_env.data.is_empty() {
523 let preview_len = std::cmp::min(64, tx_env.data.len());
524 eprintln!(
525 " Data preview: 0x{}{}",
526 hex::encode(&tx_env.data[..preview_len]),
527 if tx_env.data.len() > 64 { "..." } else { "" }
528 );
529 }
530 eprintln!(" Nonce: {}", tx_env.nonce);
531 eprintln!(" Chain ID: {:?}", tx_env.chain_id);
532 eprintln!(" Access list: {} entries", tx_env.access_list.len());
533 if !tx_env.access_list.is_empty() {
534 for (i, access) in tx_env.access_list.iter().enumerate().take(3) {
535 eprintln!(
536 " Access {}: address={:?}, {} storage keys",
537 i,
538 access.address,
539 access.storage_keys.len()
540 );
541 }
542 if tx_env.access_list.len() > 3 {
543 eprintln!(
544 " ... and {} more access list entries",
545 tx_env.access_list.len() - 3
546 );
547 }
548 }
549 eprintln!(" Blob hashes: {} blobs", tx_env.blob_hashes.len());
550 if !tx_env.blob_hashes.is_empty() {
551 for (i, hash) in tx_env.blob_hashes.iter().enumerate().take(3) {
552 eprintln!(" Blob {i}: {hash:?}");
553 }
554 if tx_env.blob_hashes.len() > 3 {
555 eprintln!(
556 " ... and {} more blob hashes",
557 tx_env.blob_hashes.len() - 3
558 );
559 }
560 }
561 eprintln!(" Max fee per blob gas: {}", tx_env.max_fee_per_blob_gas);
562 eprintln!(
563 " Authorization list: {} items",
564 tx_env.authorization_list.len()
565 );
566 if !tx_env.authorization_list.is_empty() {
567 eprintln!(" (EIP-7702 authorizations present)");
568 }
569 } else {
570 eprintln!(
571 "\nš Transaction Environment: Not available (error occurred before tx creation)"
572 );
573 }
574
575 eprintln!("\nš¾ Pre-State (Initial):");
577 let mut sorted_accounts: Vec<_> = debug_info.pre_state.iter().collect();
579 sorted_accounts.sort_by_key(|(addr, _)| *addr);
580 for (address, (info, storage)) in sorted_accounts {
581 eprintln!(" Account {address:?}:");
582 eprintln!(" Balance: 0x{:x}", info.balance);
583 eprintln!(" Nonce: {}", info.nonce);
584 eprintln!(" Code hash: {:?}", info.code_hash);
585 eprintln!(
586 " Code size: {} bytes",
587 info.code.as_ref().map_or(0, |c| c.len())
588 );
589 if !storage.is_empty() {
590 eprintln!(" Storage ({} slots):", storage.len());
591 let mut sorted_storage: Vec<_> = storage.iter().collect();
592 sorted_storage.sort_by_key(|(key, _)| *key);
593 for (key, value) in sorted_storage.iter() {
594 eprintln!(" {key:?} => {value:?}");
595 }
596 }
597 }
598
599 eprintln!("\nš Current State (Actual):");
600 let committed_state = DebugInfo::capture_committed_state(current_state);
601 let mut sorted_current: Vec<_> = committed_state.iter().collect();
603 sorted_current.sort_by_key(|(addr, _)| *addr);
604 for (address, (info, storage)) in sorted_current {
605 eprintln!(" Account {address:?}:");
606 eprintln!(" Balance: 0x{:x}", info.balance);
607 eprintln!(" Nonce: {}", info.nonce);
608 eprintln!(" Code hash: {:?}", info.code_hash);
609 eprintln!(
610 " Code size: {} bytes",
611 info.code.as_ref().map_or(0, |c| c.len())
612 );
613 if !storage.is_empty() {
614 eprintln!(" Storage ({} slots):", storage.len());
615 let mut sorted_storage: Vec<_> = storage.iter().collect();
616 sorted_storage.sort_by_key(|(key, _)| *key);
617 for (key, value) in sorted_storage.iter() {
618 eprintln!(" {key:?} => {value:?}");
619 }
620 }
621 }
622
623 if let Some(expected_post_state) = expected_post_state {
625 eprintln!("\nā
Expected Post-State:");
626 for (address, account) in expected_post_state {
627 eprintln!(" Account {address:?}:");
628 eprintln!(" Balance: 0x{:x}", account.balance);
629 eprintln!(" Nonce: {}", account.nonce);
630 if !account.code.is_empty() {
631 eprintln!(" Code size: {} bytes", account.code.len());
632 }
633 if !account.storage.is_empty() {
634 eprintln!(" Storage ({} slots):", account.storage.len());
635 for (key, value) in account.storage.iter() {
636 eprintln!(" {key:?} => {value:?}");
637 }
638 }
639 }
640 }
641
642 eprintln!("\n===========================================\n");
643}
644
645fn execute_blockchain_test(
647 test_case: &BlockchainTestCase,
648 print_env_on_error: bool,
649 json_output: bool,
650) -> Result<(), TestExecutionError> {
651 if matches!(
653 test_case.network,
654 ForkSpec::ByzantiumToConstantinopleAt5
655 | ForkSpec::ParisToShanghaiAtTime15k
656 | ForkSpec::ShanghaiToCancunAtTime15k
657 | ForkSpec::CancunToPragueAtTime15k
658 | ForkSpec::PragueToOsakaAtTime15k
659 | ForkSpec::BPO1ToBPO2AtTime15k
660 ) {
661 eprintln!("ā ļø Skipping transition fork: {:?}", test_case.network);
662 return Ok(());
663 }
664
665 let mut state = State::builder().with_bal_builder().build();
667
668 let mut pre_state_debug = AddressMap::default();
670
671 let genesis_state = test_case.pre.clone().into_genesis_state();
673 for (address, account) in genesis_state {
674 let account_info = AccountInfo {
675 balance: account.balance,
676 nonce: account.nonce,
677 code_hash: revm::primitives::keccak256(&account.code),
678 code: Some(Bytecode::new_raw(account.code.clone())),
679 account_id: None,
680 };
681
682 if print_env_on_error {
684 pre_state_debug.insert(address, (account_info.clone(), account.storage.clone()));
685 }
686
687 state.insert_account_with_storage(address, account_info, account.storage);
688 }
689
690 state
692 .block_hashes
693 .insert(0, test_case.genesis_block_header.hash);
694
695 let spec_id = fork_to_spec_id(test_case.network);
697 let mut cfg = CfgEnv::default();
698 cfg.set_spec_and_mainnet_gas_params(spec_id);
699
700 let mut parent_block_hash = Some(test_case.genesis_block_header.hash);
702 let mut parent_excess_blob_gas = test_case
703 .genesis_block_header
704 .excess_blob_gas
705 .unwrap_or_default()
706 .to::<u64>();
707 let mut block_env = test_case.genesis_block_env();
708
709 for (block_idx, block) in test_case.blocks.iter().enumerate() {
711 println!("Run block {block_idx}/{}", test_case.blocks.len());
712
713 let should_fail = block.expect_exception.is_some();
715
716 let transactions = block.transactions.as_deref().unwrap_or_default();
717
718 let mut block_hash = None;
721 let mut beacon_root = None;
722 let this_excess_blob_gas;
723
724 if let Some(block_header) = block.block_header.as_ref() {
725 block_hash = Some(block_header.hash);
726 beacon_root = block_header.parent_beacon_block_root;
727 block_env = block_header.to_block_env(Some(BlobExcessGasAndPrice::new_with_spec(
728 parent_excess_blob_gas,
729 spec_id,
730 )));
731 this_excess_blob_gas = block_header.excess_blob_gas.map(|i| i.to::<u64>());
732 } else {
733 this_excess_blob_gas = None;
734 }
735
736 let bal_test = block
737 .block_access_list
738 .as_ref()
739 .and_then(|bal| Bal::try_from(bal.clone()).ok())
740 .map(Arc::new);
741
742 state.reset_bal_index();
744
745 let evm_context = Context::mainnet()
747 .with_block(&block_env)
748 .with_cfg(cfg.clone())
749 .with_db(&mut state);
750
751 let mut evm = evm_context.build_mainnet_with_inspector(TracerEip3155::new_stdout());
753
754 pre_block::pre_block_transition(&mut evm, spec_id, parent_block_hash, beacon_root);
756
757 for (tx_idx, tx) in transactions.iter().enumerate() {
759 if tx.sender.is_none() {
760 if print_env_on_error {
761 let debug_info = DebugInfo {
762 pre_state: pre_state_debug.clone(),
763 tx_env: None,
764 block_env: block_env.clone(),
765 cfg_env: cfg.clone(),
766 block_idx,
767 tx_idx,
768 withdrawals: block.withdrawals.clone(),
769 };
770 print_error_with_state(
771 &debug_info,
772 evm.ctx().db_ref(),
773 test_case.post_state.as_ref(),
774 );
775 }
776 if json_output {
777 let output = json!({
778 "block": block_idx,
779 "tx": tx_idx,
780 "error": "missing sender",
781 "status": "skipped"
782 });
783 print_json(&output);
784 } else {
785 eprintln!("ā ļø Skipping block {block_idx} due to missing sender");
786 }
787 break; }
789
790 let tx_env = match tx.to_tx_env() {
791 Ok(env) => env,
792 Err(e) => {
793 if should_fail {
794 continue;
796 }
797 if print_env_on_error {
798 let debug_info = DebugInfo {
799 pre_state: pre_state_debug.clone(),
800 tx_env: None,
801 block_env: block_env.clone(),
802 cfg_env: cfg.clone(),
803 block_idx,
804 tx_idx,
805 withdrawals: block.withdrawals.clone(),
806 };
807 print_error_with_state(
808 &debug_info,
809 evm.ctx().db_ref(),
810 test_case.post_state.as_ref(),
811 );
812 }
813 if json_output {
814 let output = json!({
815 "block": block_idx,
816 "tx": tx_idx,
817 "error": format!("tx env creation error: {e}"),
818 "status": "skipped"
819 });
820 print_json(&output);
821 } else {
822 eprintln!(
823 "ā ļø Skipping block {block_idx} due to transaction env creation error: {e}"
824 );
825 }
826 break; }
828 };
829
830 evm.db_mut().bump_bal_index();
832
833 let execution_result = if json_output {
835 evm.inspect_tx(tx_env.clone())
836 } else {
837 evm.transact(tx_env.clone())
838 };
839
840 match execution_result {
841 Ok(result) => {
842 if should_fail {
843 if print_env_on_error {
846 if json_output {
848 eprintln!("=== Transaction trace (unexpected success) ===");
849 }
850 let _ = evm.inspect_tx(tx_env.clone());
851 }
852
853 if print_env_on_error {
854 let debug_info = DebugInfo {
855 pre_state: pre_state_debug.clone(),
856 tx_env: Some(tx_env.clone()),
857 block_env: block_env.clone(),
858 cfg_env: cfg.clone(),
859 block_idx,
860 tx_idx,
861 withdrawals: block.withdrawals.clone(),
862 };
863 print_error_with_state(
864 &debug_info,
865 evm.ctx().db_ref(),
866 test_case.post_state.as_ref(),
867 );
868 }
869 let expected_exception = block.expect_exception.clone().unwrap_or_default();
870 if json_output {
871 let output = json!({
872 "block": block_idx,
873 "tx": tx_idx,
874 "expected_exception": expected_exception,
875 "gas_used": result.result.gas_used(),
876 "status": "unexpected_success"
877 });
878 print_json(&output);
879 } else {
880 eprintln!(
881 "ā ļø Skipping block {block_idx}: transaction unexpectedly succeeded (expected failure: {expected_exception})"
882 );
883 }
884 break; }
886 evm.commit(result.state);
887 }
888 Err(e) => {
889 if !should_fail {
890 if print_env_on_error {
892 if json_output {
893 eprintln!("=== Transaction trace (unexpected failure) ===");
894 }
895 let _ = evm.inspect_tx(tx_env.clone());
896 }
897
898 if print_env_on_error {
899 let debug_info = DebugInfo {
900 pre_state: pre_state_debug.clone(),
901 tx_env: Some(tx_env.clone()),
902 block_env: block_env.clone(),
903 cfg_env: cfg.clone(),
904 block_idx,
905 tx_idx,
906 withdrawals: block.withdrawals.clone(),
907 };
908 print_error_with_state(
909 &debug_info,
910 evm.ctx().db_ref(),
911 test_case.post_state.as_ref(),
912 );
913 }
914 if json_output {
915 let output = json!({
916 "block": block_idx,
917 "tx": tx_idx,
918 "error": format!("{e:?}"),
919 "status": "unexpected_failure"
920 });
921 print_json(&output);
922 } else {
923 eprintln!(
924 "ā ļø Skipping block {block_idx} due to unexpected failure: {e:?}"
925 );
926 }
927 break; } else if json_output {
929 let output = json!({
931 "block": block_idx,
932 "tx": tx_idx,
933 "error": format!("{e:?}"),
934 "status": "expected_failure"
935 });
936 print_json(&output);
937 }
938 }
939 }
940 }
941
942 evm.db_mut().bump_bal_index();
944
945 post_block::post_block_transition(
947 &mut evm,
948 &block_env,
949 block.withdrawals.as_deref().unwrap_or_default(),
950 spec_id,
951 );
952
953 state
955 .block_hashes
956 .insert(block_env.number.to::<u64>(), block_hash.unwrap_or_default());
957
958 if let Some(bal) = state.bal_state.bal_builder.take() {
959 if let Some(state_bal) = bal_test {
960 if &bal != state_bal.as_ref() {
961 println!("Bal mismatch");
962 println!("Test bal");
963 state_bal.pretty_print();
964 println!("Bal:");
965 bal.pretty_print();
966 return Err(TestExecutionError::BalMismatchError);
967 }
968 }
969 }
970
971 parent_block_hash = block_hash;
972 if let Some(excess_blob_gas) = this_excess_blob_gas {
973 parent_excess_blob_gas = excess_blob_gas;
974 }
975
976 state.merge_transitions(BundleRetention::Reverts);
977 }
978
979 if let Some(expected_post_state) = &test_case.post_state {
981 let debug_info = DebugInfo {
983 pre_state: pre_state_debug.clone(),
984 tx_env: None, block_env: block_env.clone(),
986 cfg_env: cfg.clone(),
987 block_idx: test_case.blocks.len(),
988 tx_idx: 0,
989 withdrawals: test_case.blocks.last().and_then(|b| b.withdrawals.clone()),
990 };
991 validate_post_state(
992 &mut state,
993 expected_post_state,
994 &debug_info,
995 print_env_on_error,
996 )?;
997 }
998
999 Ok(())
1000}
1001
1002fn fork_to_spec_id(fork: ForkSpec) -> SpecId {
1004 match fork {
1005 ForkSpec::Frontier => SpecId::FRONTIER,
1006 ForkSpec::Homestead | ForkSpec::FrontierToHomesteadAt5 => SpecId::HOMESTEAD,
1007 ForkSpec::EIP150 | ForkSpec::HomesteadToDaoAt5 | ForkSpec::HomesteadToEIP150At5 => {
1008 SpecId::TANGERINE
1009 }
1010 ForkSpec::EIP158 => SpecId::SPURIOUS_DRAGON,
1011 ForkSpec::Byzantium
1012 | ForkSpec::EIP158ToByzantiumAt5
1013 | ForkSpec::ByzantiumToConstantinopleFixAt5 => SpecId::BYZANTIUM,
1014 ForkSpec::Constantinople | ForkSpec::ByzantiumToConstantinopleAt5 => SpecId::PETERSBURG,
1015 ForkSpec::ConstantinopleFix => SpecId::PETERSBURG,
1016 ForkSpec::Istanbul => SpecId::ISTANBUL,
1017 ForkSpec::Berlin => SpecId::BERLIN,
1018 ForkSpec::London | ForkSpec::BerlinToLondonAt5 => SpecId::LONDON,
1019 ForkSpec::Paris | ForkSpec::ParisToShanghaiAtTime15k => SpecId::MERGE,
1020 ForkSpec::Shanghai => SpecId::SHANGHAI,
1021 ForkSpec::Cancun | ForkSpec::ShanghaiToCancunAtTime15k => SpecId::CANCUN,
1022 ForkSpec::Prague | ForkSpec::CancunToPragueAtTime15k => SpecId::PRAGUE,
1023 ForkSpec::Osaka | ForkSpec::PragueToOsakaAtTime15k => SpecId::OSAKA,
1024 ForkSpec::Amsterdam => SpecId::AMSTERDAM,
1025 _ => SpecId::AMSTERDAM, }
1027}
1028
1029fn skip_test(path: &Path) -> bool {
1031 let path_str = path.to_str().unwrap_or_default();
1032 if path_str.contains("paris/eip7610_create_collision")
1034 || path_str.contains("cancun/eip4844_blobs")
1035 || path_str.contains("prague/eip7251_consolidations")
1036 || path_str.contains("prague/eip7685_general_purpose_el_requests")
1037 || path_str.contains("prague/eip7002_el_triggerable_withdrawals")
1038 || path_str.contains("osaka/eip7918_blob_reserve_price")
1039 {
1040 return true;
1041 }
1042
1043 let name = path.file_name().unwrap().to_str().unwrap_or_default();
1044 matches!(
1046 name,
1047 "CreateTransactionHighNonce.json"
1049
1050 | "RevertInCreateInInit_Paris.json"
1052 | "RevertInCreateInInit.json"
1053 | "dynamicAccountOverwriteEmpty.json"
1054 | "dynamicAccountOverwriteEmpty_Paris.json"
1055 | "RevertInCreateInInitCreate2Paris.json"
1056 | "create2collisionStorage.json"
1057 | "RevertInCreateInInitCreate2.json"
1058 | "create2collisionStorageParis.json"
1059 | "InitCollision.json"
1060 | "InitCollisionParis.json"
1061
1062 | "ValueOverflow.json"
1064 | "ValueOverflowParis.json"
1065
1066 | "Call50000_sha256.json"
1068 | "static_Call50000_sha256.json"
1069 | "loopMul.json"
1070 | "CALLBlake2f_MaxRounds.json"
1071 | "scenarios.json"
1073 | "invalid_tx_max_fee_per_blob_gas.json"
1075 | "correct_increasing_blob_gas_costs.json"
1076 | "correct_decreasing_blob_gas_costs.json"
1077
1078 | "block_hashes_history.json"
1080 )
1081}
1082
1083#[derive(Debug, Error)]
1084pub enum TestExecutionError {
1085 #[error("Database error: {0}")]
1086 Database(String),
1087
1088 #[error("Skipped fork: {0}")]
1089 SkippedFork(String),
1090
1091 #[error("Sender is required")]
1092 SenderRequired,
1093
1094 #[error("Expected failure at block {block_idx}, tx {tx_idx}: {message}")]
1095 ExpectedFailure {
1096 block_idx: usize,
1097 tx_idx: usize,
1098 message: String,
1099 },
1100
1101 #[error("Unexpected failure at block {block_idx}, tx {tx_idx}: {error}")]
1102 UnexpectedFailure {
1103 block_idx: usize,
1104 tx_idx: usize,
1105 error: String,
1106 },
1107
1108 #[error("Transaction env creation failed at block {block_idx}, tx {tx_idx}: {error}")]
1109 TransactionEnvCreation {
1110 block_idx: usize,
1111 tx_idx: usize,
1112 error: String,
1113 },
1114
1115 #[error("Unexpected revert at block {block_idx}, tx {tx_idx}, gas used: {gas_used}")]
1116 UnexpectedRevert {
1117 block_idx: usize,
1118 tx_idx: usize,
1119 gas_used: u64,
1120 },
1121
1122 #[error("Unexpected halt at block {block_idx}, tx {tx_idx}: {reason:?}, gas used: {gas_used}")]
1123 UnexpectedHalt {
1124 block_idx: usize,
1125 tx_idx: usize,
1126 reason: HaltReason,
1127 gas_used: u64,
1128 },
1129
1130 #[error("BAL error")]
1131 BalMismatchError,
1132
1133 #[error(
1134 "Post-state validation failed for {address:?}.{field}: expected {expected}, got {actual}"
1135 )]
1136 PostStateValidation {
1137 address: Address,
1138 field: String,
1139 expected: String,
1140 actual: String,
1141 },
1142}
1143
1144#[derive(Debug, Error)]
1145pub enum Error {
1146 #[error("Path not found: {0}")]
1147 PathNotFound(PathBuf),
1148
1149 #[error("No JSON files found in: {0}")]
1150 NoJsonFiles(PathBuf),
1151
1152 #[error("Failed to read file {0}: {1}")]
1153 FileRead(PathBuf, std::io::Error),
1154
1155 #[error("Failed to decode JSON from {0}: {1}")]
1156 JsonDecode(PathBuf, serde_json::Error),
1157
1158 #[error("Test execution failed for {test_name} in {test_path}: {error}")]
1159 TestExecution {
1160 test_name: String,
1161 test_path: PathBuf,
1162 error: String,
1163 },
1164
1165 #[error("Directory traversal error: {0}")]
1166 WalkDir(#[from] walkdir::Error),
1167
1168 #[error("{failed} tests failed")]
1169 TestsFailed { failed: usize },
1170}