1use crate::merkle_trie::{compute_test_roots, TestValidationResult};
5use indicatif::{ProgressBar, ProgressDrawTarget};
6use revm::{
7 context::{block::BlockEnv, cfg::CfgEnv, tx::TxEnv},
8 context_interface::result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction},
9 database::{self, bal::EvmDatabaseError},
10 database_interface::EmptyDB,
11 primitives::{hardfork::SpecId, Bytes, B256, U256},
12 statetest_types::{SpecName, Test, TestSuite, TestUnit},
13 Context, ExecuteCommitEvm, MainBuilder, MainContext,
14};
15use serde_json::json;
16use std::{
17 convert::Infallible,
18 fmt::Debug,
19 path::{Path, PathBuf},
20 sync::{
21 atomic::{AtomicBool, AtomicUsize, Ordering},
22 Arc, Mutex,
23 },
24 time::{Duration, Instant},
25};
26use thiserror::Error;
27
28#[derive(Debug, Error)]
30#[error("Path: {path}\nName: {name}\nError: {kind}")]
31pub struct TestError {
32 pub name: String,
33 pub path: String,
34 pub kind: TestErrorKind,
35}
36
37#[derive(Debug, Error)]
39pub enum TestErrorKind {
40 #[error("logs root mismatch: got {got}, expected {expected}")]
41 LogsRootMismatch { got: B256, expected: B256 },
42 #[error("state root mismatch: got {got}, expected {expected}")]
43 StateRootMismatch { got: B256, expected: B256 },
44 #[error("unknown private key: {0:?}")]
45 UnknownPrivateKey(B256),
46 #[error("unexpected exception: got {got_exception:?}, expected {expected_exception:?}")]
47 UnexpectedException { expected_exception: Option<String>, got_exception: Option<String> },
48 #[error("unexpected output: got {got_output:?}, expected {expected_output:?}")]
49 UnexpectedOutput { expected_output: Option<Bytes>, got_output: Option<Bytes> },
50 #[error(transparent)]
51 SerdeDeserialize(#[from] serde_json::Error),
52 #[error("thread panicked")]
53 Panic,
54 #[error("path does not exist")]
55 InvalidPath,
56 #[error("no JSON test files found in path")]
57 NoJsonFiles,
58 #[error("compilation failed: {0}")]
59 CompilationError(String),
60}
61
62pub fn skip_test(path: &Path) -> bool {
65 let path_str = path.to_str().unwrap_or_default();
66
67 if path_str.contains("paris/eip7610_create_collision") {
69 return true;
70 }
71
72 let name = path.file_name().unwrap().to_str().unwrap_or_default();
73
74 matches!(
75 name,
76 | "CreateTransactionHighNonce.json"
79
80 | "RevertInCreateInInit_Paris.json"
82 | "RevertInCreateInInit.json"
83 | "dynamicAccountOverwriteEmpty.json"
84 | "dynamicAccountOverwriteEmpty_Paris.json"
85 | "RevertInCreateInInitCreate2Paris.json"
86 | "create2collisionStorage.json"
87 | "RevertInCreateInInitCreate2.json"
88 | "create2collisionStorageParis.json"
89 | "InitCollision.json"
90 | "InitCollisionParis.json"
91 | "test_init_collision_create_opcode.json"
92
93 | "ValueOverflow.json"
95 | "ValueOverflowParis.json"
96
97 | "Call50000_sha256.json"
99 | "static_Call50000_sha256.json"
100 | "loopMul.json"
101 | "CALLBlake2f_MaxRounds.json"
102 )
103}
104
105struct TestExecutionContext<'a> {
106 name: &'a str,
107 unit: &'a TestUnit,
108 test: &'a Test,
109 cfg: &'a CfgEnv,
110 block: &'a BlockEnv,
111 tx: &'a TxEnv,
112 cache_state: &'a database::CacheState,
113 elapsed: &'a Arc<Mutex<Duration>>,
114 #[allow(dead_code)]
115 trace: bool,
116 print_json_outcome: bool,
117}
118
119fn build_json_output(
120 test: &Test,
121 test_name: &str,
122 exec_result: &Result<
123 ExecutionResult<HaltReason>,
124 EVMError<EvmDatabaseError<Infallible>, InvalidTransaction>,
125 >,
126 validation: &TestValidationResult,
127 spec: SpecId,
128 error: Option<String>,
129) -> serde_json::Value {
130 json!({
131 "stateRoot": validation.state_root,
132 "logsRoot": validation.logs_root,
133 "output": exec_result.as_ref().ok().and_then(|r| r.output().cloned()).unwrap_or_default(),
134 "gasUsed": exec_result.as_ref().ok().map(|r| r.gas_used()).unwrap_or_default(),
135 "pass": error.is_none(),
136 "errorMsg": error.unwrap_or_default(),
137 "evmResult": format_evm_result(exec_result),
138 "postLogsHash": validation.logs_root,
139 "fork": spec,
140 "test": test_name,
141 "d": test.indexes.data,
142 "g": test.indexes.gas,
143 "v": test.indexes.value,
144 })
145}
146
147fn format_evm_result(
148 exec_result: &Result<
149 ExecutionResult<HaltReason>,
150 EVMError<EvmDatabaseError<Infallible>, InvalidTransaction>,
151 >,
152) -> String {
153 match exec_result {
154 Ok(r) => match r {
155 ExecutionResult::Success { reason, .. } => format!("Success: {reason:?}"),
156 ExecutionResult::Revert { .. } => "Revert".to_string(),
157 ExecutionResult::Halt { reason, .. } => format!("Halt: {reason:?}"),
158 },
159 Err(e) => e.to_string(),
160 }
161}
162
163fn validate_exception(
164 test: &Test,
165 exec_result: &Result<
166 ExecutionResult<HaltReason>,
167 EVMError<EvmDatabaseError<Infallible>, InvalidTransaction>,
168 >,
169) -> Result<bool, TestErrorKind> {
170 match (&test.expect_exception, exec_result) {
171 (None, Ok(_)) => Ok(false),
172 (Some(_), Err(_)) => Ok(true),
173 _ => Err(TestErrorKind::UnexpectedException {
174 expected_exception: test.expect_exception.clone(),
175 got_exception: exec_result.as_ref().err().map(|e| e.to_string()),
176 }),
177 }
178}
179
180fn validate_output(
181 expected_output: Option<&Bytes>,
182 actual_result: &ExecutionResult<HaltReason>,
183) -> Result<(), TestErrorKind> {
184 if let Some((expected, actual)) = expected_output.zip(actual_result.output()) {
185 if expected != actual {
186 return Err(TestErrorKind::UnexpectedOutput {
187 expected_output: Some(expected.clone()),
188 got_output: actual_result.output().cloned(),
189 });
190 }
191 }
192 Ok(())
193}
194
195pub(crate) fn check_evm_execution(
196 test: &Test,
197 expected_output: Option<&Bytes>,
198 test_name: &str,
199 exec_result: &Result<
200 ExecutionResult<HaltReason>,
201 EVMError<EvmDatabaseError<Infallible>, InvalidTransaction>,
202 >,
203 db: &mut database::State<EmptyDB>,
204 spec: SpecId,
205 print_json_outcome: bool,
206) -> Result<(), TestErrorKind> {
207 let validation = compute_test_roots(exec_result, db);
208
209 let print_json = |error: Option<&TestErrorKind>| {
210 if print_json_outcome {
211 let json = build_json_output(
212 test,
213 test_name,
214 exec_result,
215 &validation,
216 spec,
217 error.map(|e| e.to_string()),
218 );
219 eprintln!("{json}");
220 }
221 };
222
223 let exception_expected = validate_exception(test, exec_result).inspect_err(|e| {
225 print_json(Some(e));
226 })?;
227
228 if exception_expected {
230 print_json(None);
231 return Ok(());
232 }
233
234 if let Ok(result) = exec_result {
236 validate_output(expected_output, result).inspect_err(|e| {
237 print_json(Some(e));
238 })?;
239 }
240
241 if validation.logs_root != test.logs {
243 let error =
244 TestErrorKind::LogsRootMismatch { got: validation.logs_root, expected: test.logs };
245 print_json(Some(&error));
246 return Err(error);
247 }
248
249 if validation.state_root != test.hash {
251 let error =
252 TestErrorKind::StateRootMismatch { got: validation.state_root, expected: test.hash };
253 print_json(Some(&error));
254 return Err(error);
255 }
256
257 print_json(None);
258 Ok(())
259}
260
261pub fn execute_test_suite(
263 path: &Path,
264 elapsed: &Arc<Mutex<Duration>>,
265 trace: bool,
266 print_json_outcome: bool,
267) -> Result<(), TestError> {
268 if skip_test(path) {
269 return Ok(());
270 }
271
272 let s = std::fs::read_to_string(path).unwrap();
273 let path = path.to_string_lossy().into_owned();
274 let suite: TestSuite = serde_json::from_str(&s).map_err(|e| TestError {
275 name: "Unknown".to_string(),
276 path: path.clone(),
277 kind: e.into(),
278 })?;
279
280 for (name, unit) in suite.0 {
281 let cache_state = unit.state();
283
284 let mut cfg = CfgEnv::default();
286 cfg.chain_id = unit.env.current_chain_id.unwrap_or(U256::ONE).try_into().unwrap_or(1);
287
288 for (spec_name, tests) in &unit.post {
290 if *spec_name == SpecName::Constantinople {
292 continue;
293 }
294
295 cfg.set_spec_and_mainnet_gas_params(spec_name.to_spec_id());
296
297 if cfg.spec().is_enabled_in(SpecId::OSAKA) {
299 cfg.set_max_blobs_per_tx(6);
300 } else if cfg.spec().is_enabled_in(SpecId::PRAGUE) {
301 cfg.set_max_blobs_per_tx(9);
302 } else {
303 cfg.set_max_blobs_per_tx(6);
304 }
305
306 let block = unit.block_env(&mut cfg);
308
309 for test in tests.iter() {
310 let tx = match test.tx_env(&unit) {
312 Ok(tx) => tx,
313 Err(_) if test.expect_exception.is_some() => continue,
314 Err(_) => {
315 return Err(TestError {
316 name,
317 path,
318 kind: TestErrorKind::UnknownPrivateKey(unit.transaction.secret_key),
319 });
320 }
321 };
322
323 let result = execute_single_test(TestExecutionContext {
325 name: &name,
326 unit: &unit,
327 test,
328 cfg: &cfg,
329 block: &block,
330 tx: &tx,
331 cache_state: &cache_state,
332 elapsed,
333 trace,
334 print_json_outcome,
335 });
336
337 if let Err(e) = result {
338 static FAILED: AtomicBool = AtomicBool::new(false);
340 if print_json_outcome || FAILED.swap(true, Ordering::SeqCst) {
341 return Err(TestError { name, path, kind: e });
342 }
343
344 return Err(TestError { path, name, kind: e });
345 }
346 }
347 }
348 }
349 Ok(())
350}
351
352fn execute_single_test(ctx: TestExecutionContext) -> Result<(), TestErrorKind> {
353 let cache = ctx.cache_state.clone();
355 let mut state =
356 database::State::builder().with_cached_prestate(cache).with_bundle_update().build();
357
358 let evm_context = Context::mainnet()
359 .with_block(ctx.block)
360 .with_tx(ctx.tx)
361 .with_cfg(ctx.cfg.clone())
362 .with_db(&mut state);
363
364 let timer = Instant::now();
366 let mut evm = evm_context.build_mainnet();
367 let exec_result = evm.transact_commit(ctx.tx);
368 let db = evm.ctx.journaled_state.database;
369 *ctx.elapsed.lock().unwrap() += timer.elapsed();
370
371 check_evm_execution(
373 ctx.test,
374 ctx.unit.out.as_ref(),
375 ctx.name,
376 &exec_result,
377 db,
378 *ctx.cfg.spec(),
379 ctx.print_json_outcome,
380 )
381}
382
383#[derive(Clone)]
384pub(crate) struct TestRunnerState {
385 pub(crate) n_errors: Arc<AtomicUsize>,
386 pub(crate) console_bar: Arc<ProgressBar>,
387 pub(crate) queue: Arc<Mutex<(usize, Vec<PathBuf>)>>,
388 pub(crate) elapsed: Arc<Mutex<Duration>>,
389}
390
391impl TestRunnerState {
392 pub(crate) fn new(test_files: Vec<PathBuf>) -> Self {
393 let n_files = test_files.len();
394 Self {
395 n_errors: Arc::new(AtomicUsize::new(0)),
396 console_bar: Arc::new(ProgressBar::with_draw_target(
397 Some(n_files as u64),
398 ProgressDrawTarget::stdout(),
399 )),
400 queue: Arc::new(Mutex::new((0usize, test_files))),
401 elapsed: Arc::new(Mutex::new(Duration::ZERO)),
402 }
403 }
404
405 pub(crate) fn next_test(&self) -> Option<PathBuf> {
406 let (current_idx, queue) = &mut *self.queue.lock().unwrap();
407 let idx = *current_idx;
408 let test_path = queue.get(idx).cloned()?;
409 *current_idx = idx + 1;
410 Some(test_path)
411 }
412}