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 = if test_vector.container_kind.is_some() {
80                    Some(CodeType::ReturnContract)
81                } else {
82                    Some(CodeType::ReturnOrStop)
83                };
84                // In future this can be generalized to cover multiple forks, Not just Osaka.
85                let Some(test_result) = test_vector.results.get("Osaka") else {
86                    // if test does not have a result that we can compare to, we skip it
87                    println!("Test without result: {} - {}", name, vector_name);
88                    continue;
89                };
90                let res = validate_raw_eof_inner(test_vector.code.clone(), kind);
91                if test_result.result != res.is_ok() {
92                    println!(
93                        "\nTest failed: {} - {}\nresult:{:?}\nrevm err_result:{:#?}\nExpected exception:{:?}\nbytes:{:?}\n",
94                        name,
95                        vector_name,
96                        test_result.result,
97                        res.as_ref().err(),
98                        test_result.exception,
99                        test_vector.code
100                    );
101                    *types_of_error
102                        .entry(
103                            res.err()
104                                .map(ErrorType::Error)
105                                .unwrap_or(ErrorType::FalsePositive),
106                        )
107                        .or_default() += 1;
108                } else {
109                    passed_tests += 1;
110                }
111            }
112        }
113    }
114    println!("Passed tests: {}/{}", passed_tests, test_sum);
115    if passed_tests != test_sum {
116        println!("Types of error: {:#?}", types_of_error);
117        Err(Error::EofValidation {
118            failed_test: test_sum - passed_tests,
119            total_tests: test_sum,
120        })
121    } else {
122        Ok(())
123    }
124}