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