revme/cmd/
eofvalidation.rs

1mod test_suite;
2
3pub use test_suite::{TestResult, TestSuite, TestUnit, TestVector};
4
5use crate::{cmd::Error, dir_utils::find_all_json_tests};
6use clap::Parser;
7use revm::bytecode::eof::{validate_raw_eof_inner, CodeType, EofError};
8use std::collections::BTreeMap;
9use std::path::{Path, PathBuf};
10
11/// `eof-validation` subcommand
12#[derive(Parser, Debug)]
13pub struct Cmd {
14    /// Input paths to EOF validation tests
15    #[arg(required = true, num_args = 1..)]
16    paths: Vec<PathBuf>,
17}
18
19impl Cmd {
20    /// Runs statetest command.
21    pub fn run(&self) -> Result<(), Error> {
22        // Check if path exists.
23        for path in &self.paths {
24            if !path.exists() {
25                return Err(Error::Custom("The specified path does not exist"));
26            }
27            run_test(path)?
28        }
29        Ok(())
30    }
31}
32
33fn skip_test(name: &str) -> bool {
34    // Embedded containers rules changed
35    if name.starts_with("EOF1_embedded_container") {
36        return true;
37    }
38    matches!(
39        name,
40        "EOF1_undefined_opcodes_186"
41        | ""
42        // Truncated data is only allowed in embedded containers
43        | "validInvalid_48"
44        | "validInvalid_1"
45        | "EOF1_truncated_section_3"
46        | "EOF1_truncated_section_4"
47        | "validInvalid_2"
48        | "validInvalid_3"
49        // Orphan containers are no longer allowed
50        | "EOF1_returncontract_valid_0"
51        | "EOF1_returncontract_valid_1"
52        | "EOF1_returncontract_valid_2"
53        | "EOF1_eofcreate_valid_1"
54        | "EOF1_eofcreate_valid_2"
55        | "EOF1_section_order_6"
56    )
57}
58
59pub fn run_test(path: &Path) -> Result<(), Error> {
60    let test_files = find_all_json_tests(path);
61    let mut test_sum = 0;
62    let mut passed_tests = 0;
63
64    #[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
65    enum ErrorType {
66        FalsePositive,
67        Error(EofError),
68    }
69    let mut types_of_error: BTreeMap<ErrorType, usize> = BTreeMap::new();
70    for test_file in test_files {
71        let s = std::fs::read_to_string(&test_file).unwrap();
72        let suite: TestSuite = serde_json::from_str(&s).unwrap();
73        for (name, test_unit) in suite.0 {
74            for (vector_name, test_vector) in test_unit.vectors {
75                if skip_test(&vector_name) {
76                    continue;
77                }
78                test_sum += 1;
79                let kind = match test_vector.container_kind.as_deref() {
80                    Some("RUNTIME") => CodeType::Runtime,
81                    Some("INITCODE") => CodeType::Initcode,
82                    None => CodeType::Runtime,
83                    _ => return Err(Error::Custom("Invalid container kind")),
84                };
85                // In future this can be generalized to cover multiple forks, Not just Osaka.
86                let Some(test_result) = test_vector.results.get("Osaka") else {
87                    // if test does not have a result that we can compare to, we skip it
88                    println!("Test without result: {} - {}", name, vector_name);
89                    continue;
90                };
91                let res = validate_raw_eof_inner(test_vector.code.clone(), Some(kind));
92                if test_result.result != res.is_ok() {
93                    println!(
94                        "\nTest failed: {} - {}\nPath:{:?}\nresult:{:?}\nrevm err_result:{:#?}\nExpected exception:{:?}\nbytes:{:?}\n",
95                        name,
96                        vector_name,
97                        test_file,
98                        test_result.result,
99                        res.as_ref().err(),
100                        test_result.exception,
101                        test_vector.code
102                    );
103                    *types_of_error
104                        .entry(
105                            res.err()
106                                .map(ErrorType::Error)
107                                .unwrap_or(ErrorType::FalsePositive),
108                        )
109                        .or_default() += 1;
110                } else {
111                    passed_tests += 1;
112                }
113            }
114        }
115    }
116    println!("Passed tests: {}/{}", passed_tests, test_sum);
117    if passed_tests != test_sum {
118        println!("Types of error: {:#?}", types_of_error);
119        Err(Error::EofValidation {
120            failed_test: test_sum - passed_tests,
121            total_tests: test_sum,
122        })
123    } else {
124        Ok(())
125    }
126}