diff --git a/Cargo.lock b/Cargo.lock index 6f3e9bd..292a9ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -182,6 +182,7 @@ dependencies = [ "hex", "lazy_static", "num256", + "official_test_types", "paste", "phf", "primitive-types", @@ -362,6 +363,15 @@ dependencies = [ "serde", ] +[[package]] +name = "official_test_types" +version = "0.1.0" +dependencies = [ + "hex", + "primitive-types", + "serde", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -680,6 +690,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" name = "test_gen" version = "0.1.0" dependencies = [ + "hex", + "official_test_types", + "primitive-types", "proc-macro2", "quote", "serde", diff --git a/Cargo.toml b/Cargo.toml index 374b3a6..4fbf58a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,4 @@ -workspace = { members = ["util"] } +workspace = { members = [ "official_test_types","util"] } [package] name = "ethereum_evm" version = "0.1.0" @@ -8,6 +8,7 @@ edition = "2021" [dependencies] test_gen = { path = "./test_gen" } +official_test_types = { path = "./official_test_types" } util = { path = "./util" } array-init = "2.1.0" ethereum = "0.15.0" diff --git a/official_test_types/Cargo.toml b/official_test_types/Cargo.toml new file mode 100644 index 0000000..38e6f43 --- /dev/null +++ b/official_test_types/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "official_test_types" +version = "0.1.0" +edition = "2021" + +[dependencies] +hex = "0.4.3" +primitive-types = { version = "0.12", features = ["rlp", "serde"] } +serde = { version = "1", features = ["derive"] } +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/official_test_types/src/lib.rs b/official_test_types/src/lib.rs new file mode 100644 index 0000000..daa474e --- /dev/null +++ b/official_test_types/src/lib.rs @@ -0,0 +1,2 @@ +pub mod types; +mod util; diff --git a/official_test_types/src/types.rs b/official_test_types/src/types.rs new file mode 100644 index 0000000..2be7369 --- /dev/null +++ b/official_test_types/src/types.rs @@ -0,0 +1,217 @@ +// use ethereum_evm::util::{h256_to_u256, u256_to_h256}; +use hex::FromHex; +use primitive_types::{H256, U256}; +use serde::{Deserialize, Deserializer, Serialize}; +use std::{collections::BTreeMap, fmt}; +use crate::util::{Hex, u256_to_h256}; + +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize)] +struct WrappedU256(U256); + +impl<'de> Deserialize<'de> for WrappedU256 { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct H256Visitor; + + impl<'de> serde::de::Visitor<'de> for H256Visitor { + type Value = WrappedU256; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string representing a hex-encoded H256 value") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + let fixed_length_str: String; + let mut value_without_prefix = if value.starts_with("0x") { + &value[2..] // Skip the first two characters (0x) + } else { + value + }; + if value_without_prefix.len() % 2 == 1{ + fixed_length_str = "0".to_string() + value_without_prefix; + value_without_prefix = fixed_length_str.as_str(); + + } + let hash_bytes: Vec = match Vec::::from_hex(value_without_prefix) { + Ok(bytes) => bytes, + Err(_) => return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(value), &self)), + }; + + let mut hash = [0u8; 32]; + let num_bytes_to_copy = hash_bytes.len().min(32); + // println!("num_bytes_to_copy: {}", num_bytes_to_copy); + // println!("hash_bytes: {:x?}", hash_bytes); + hash[32 - num_bytes_to_copy..32].copy_from_slice(&hash_bytes); + // println!("hash: {:x?}", hash); + Ok(WrappedU256(U256::from(hash))) + } + } + + deserializer.deserialize_str(H256Visitor) + } +} + + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TestStateMulti { + #[serde(rename = "_info")] + pub info: TestInfo, + pub env: TestEnv, + pub post: BTreeMap>, + pub pre: BTreeMap, + pub transaction: TestTransactionMulti, +} + +impl TestStateMulti { + pub fn tests(&self) -> Vec { + let mut tests = Vec::new(); + for (fork, post_states) in &self.post { + if fork == "Berlin" { + let mut new_tests: Vec = post_states + .iter() + .enumerate() + .map(|(index, post_state)| TestState { + info: self.info.clone(), + env: self.env.clone(), + fork: fork.clone(), + post: post_state.clone(), + pre: self.pre.clone(), + transaction: TestTransaction { + data: self.transaction.data[post_state.indexes.data].0.clone(), + gas_limit: self.transaction.gas_limit[post_state.indexes.gas], + gas_price: self.transaction.gas_price, + max_fee_per_gas: self.transaction.max_fee_per_gas, + max_priority_fee_per_gas: self.transaction.max_priority_fee_per_gas, + nonce: self.transaction.nonce, + secret_key: self.transaction.secret_key, + sender: self.transaction.sender, + to: self.transaction.to, + value: self.transaction.value[post_state.indexes.value], + }, + }) + .collect(); + tests.append(&mut new_tests); + } + } + tests + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TestState { + #[serde(rename = "_info")] + pub info: TestInfo, + pub env: TestEnv, + pub fork: String, + pub post: TestPost, + pub pre: BTreeMap, + pub transaction: TestTransaction, +} + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TestInfo { + pub comment: String, + #[serde(rename = "filling-rpc-server")] + pub filling_rpc_server: String, + #[serde(rename = "filling-tool-version")] + pub filling_tool_version: String, + pub labels: Option>, + pub generated_test_hash: String, + #[serde(rename = "lllcversion")] + pub lllc_version: String, + pub solidity: String, + pub source: String, + pub source_hash: String, +} + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TestEnv { + pub current_base_fee: U256, + pub current_coinbase: U256, + pub current_difficulty: U256, + pub current_excess_blob_gas: U256, + pub current_gas_limit: U256, + pub current_number: U256, + pub current_random: U256, + pub current_timestamp: U256, +} + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TestPost { + pub hash: H256, + pub indexes: TestPostIndexes, + pub logs: U256, + #[serde(rename = "txbytes")] + pub tx_bytes: Hex, +} + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TestPostIndexes { + pub data: usize, + pub gas: usize, + pub value: usize, +} + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TestContract { + balance: WrappedU256, + pub code: Hex, + nonce: WrappedU256, + storage: BTreeMap, +} + +impl TestContract{ + pub fn storage(&self) -> BTreeMap { + self.storage.iter().map(|(k, v)| (u256_to_h256(k.0), u256_to_h256(v.0))).collect() + } + + pub fn nonce(&self) -> U256 { + self.nonce.0 + } + + pub fn balance(&self) -> U256 { + self.balance.0 + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TestTransactionMulti { + pub data: Vec, + pub gas_limit: Vec, + pub gas_price: Option, + pub max_fee_per_gas: Option, + pub max_priority_fee_per_gas: Option, + pub nonce: U256, + pub secret_key: U256, + pub to: U256, + pub sender: U256, + pub value: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TestTransaction { + pub data: Vec, + pub gas_limit: U256, + pub gas_price: Option, + pub max_fee_per_gas: Option, + pub max_priority_fee_per_gas: Option, + pub nonce: U256, + pub secret_key: U256, + pub sender: U256, + pub to: U256, + pub value: U256, +} diff --git a/official_test_types/src/util.rs b/official_test_types/src/util.rs new file mode 100644 index 0000000..3cbbf41 --- /dev/null +++ b/official_test_types/src/util.rs @@ -0,0 +1,70 @@ +use hex::FromHex; +use serde::{ + de::{Error, Visitor}, ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer +}; +use primitive_types::{H256, U256}; +use std::fmt; + + +pub fn u256_to_h256(v: U256) -> H256 { + let mut r = H256::default(); + v.to_big_endian(&mut r[..]); + r +} + + +// TODO reimplement; + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +pub struct Hex(#[serde(deserialize_with = "deserialize_hex_bytes", serialize_with = "serialize_hex_bytes")] pub Vec); + +fn deserialize_hex_bytes<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + struct HexStrVisitor; + + impl<'de> Visitor<'de> for HexStrVisitor { + type Value = Vec; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "a hex encoded string") + } + + fn visit_str(self, data: &str) -> Result + where + E: Error, + { + if &data[0..2] != "0x" { + return Err(Error::custom("should start with 0x")); + } + + FromHex::from_hex(&data[2..]).map_err(Error::custom) + } + + fn visit_borrowed_str(self, data: &'de str) -> Result + where + E: Error, + { + if &data[0..2] != "0x" { + return Err(Error::custom("should start with 0x")); + } + + FromHex::from_hex(&data[2..]).map_err(Error::custom) + } + } + + deserializer.deserialize_str(HexStrVisitor) +} + +fn serialize_hex_bytes(data: &Vec, serializer: S) -> Result +where + S: Serializer, +{ + let hex_string: String = data.iter() + .map(|byte| format!("{:02X}", byte)) + .collect(); + let hex_string = "0x".to_string() + &hex_string; + + serializer.serialize_str(&hex_string) +} diff --git a/src/evm_logic/evm.rs b/src/evm_logic/evm.rs index 1f060bb..1561fe7 100644 --- a/src/evm_logic/evm.rs +++ b/src/evm_logic/evm.rs @@ -44,7 +44,6 @@ pub struct EVMContext { } impl EVMContext { - #[inline] fn create_sub_context( address: U256, @@ -88,14 +87,15 @@ impl EVMContext { #[inline] fn execute_program(&mut self, runtime: &mut impl Runtime, debug: bool) -> ExecutionResult { runtime.add_context(); - let mut result; if self.program.len() != 0 { loop { result = self.execute_next_instruction(runtime, debug); match &result { - ExecutionResult::InProgress => {}, - _ => {break;} + ExecutionResult::InProgress => {} + _ => { + break; + } } } } else { @@ -136,8 +136,7 @@ impl EVMContext { #[inline] fn check_gas_usage(&self) -> ExecutionResult { - match !self.gas_recorder.is_valid_with_refunds() - { + match !self.gas_recorder.is_valid_with_refunds() { true => ExecutionResult::Error(ExecutionError::InsufficientGas), false => ExecutionResult::InProgress, } @@ -164,6 +163,7 @@ pub fn execute_transaction( value: value, data: data.to_vec(), }; + let transaction = Transaction { origin: origin, gas_price: gas_price, diff --git a/test_gen/Cargo.toml b/test_gen/Cargo.toml index f261020..c4ec5ee 100644 --- a/test_gen/Cargo.toml +++ b/test_gen/Cargo.toml @@ -6,11 +6,15 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +hex = "0.4.3" +primitive-types = "0.12.2" proc-macro2 = "1.0.79" quote = "1.0.35" serde = { version = "1.0", features = ["derive"]} serde_json = "1.0.114" syn = "2.0.52" +official_test_types = { path = "../official_test_types" } + [lib] proc-macro = true diff --git a/test_gen/src/lib.rs b/test_gen/src/lib.rs index 4c33374..5c329de 100644 --- a/test_gen/src/lib.rs +++ b/test_gen/src/lib.rs @@ -1,247 +1,321 @@ -use std::{collections::HashMap, path::Path}; - +use official_test_types::types::TestStateMulti; use proc_macro::TokenStream; use quote::quote; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; -use std::fs; +use std::collections::BTreeMap; +use std::fs::{self, File}; +use std::io::BufReader; +use std::{collections::HashMap, path::Path}; use syn::{parse_macro_input, Ident, LitStr}; -#[derive(Serialize, Deserialize, Debug)] -struct BasicTestObject { - name: String, - code: String, - result_address: String, - result_value: String, -} -#[proc_macro] -pub fn generate_tests(input: TokenStream) -> TokenStream { - let folder_name_lit = parse_macro_input!(input as LitStr); - let folder_name = folder_name_lit.value(); - let mut tests = Vec::new(); - for entry in fs::read_dir(folder_name).unwrap() { - let entry = entry.unwrap(); - let file_name = String::from(entry.path().to_str().unwrap()); - // Read the content of the specified JSON file - let input_str = match std::fs::read_to_string(&file_name) { - Ok(content) => content, - Err(err) => { - // Handle file reading error here - panic!("Failed to read file {}: {:?}", file_name, err); - } - }; - let input: Vec = match serde_json::from_str(&input_str) { - Ok(obj) => obj, - Err(err) => { - // Handle parsing error here - panic!("Failed to parse input from file {}: {:?}", file_name, err); - } - }; - - for (_, test) in input.into_iter().enumerate() { - let test_name = Ident::new(test.name.as_str(), proc_macro2::Span::call_site()); - let code = test.code.as_str(); - let result_address = test.result_address; - let result_value = test.result_value; - tests.push(quote! { - #[test] - fn #test_name() { - - let code = assemble(String::from(#code)); - let contract = Contract { - balance: U256::from(10 as u64), - code_size: U256::from(code.len() as u64), - code_hash: util::keccak256(&code), - code: code, - nonce: U256::from(0 as u64), - storage: BTreeMap::new(), - is_deleted: false, - is_cold: false, - hot_keys: HashSet::new(), - }; - let mut contracts: BTreeMap = BTreeMap::new(); - contracts.insert(U256::from(1 as u64), contract); - let mut mock_runtime = MockRuntime { - block_hashes: BTreeMap::new(), - block_number: U256::from(0 as u64), - block_coinbase: U256::from(0 as u64), - block_timestamp: U256::from(0 as u64), - block_difficulty: U256::from(0 as u64), - block_randomness: U256::from(0 as u64), - block_gas_limit: U256::from(100000 as u64), - block_base_fee_per_gas: U256::from(1 as u64), - chain_id: U256::from(0 as u64), - contracts: contracts, - }; - - let mut context = EVMContext::create_sub_context( - U256::from(1 as u64), - Message { - caller: U256::from(0 as u64), - value: U256::from(10 as u64), - data: Memory::new(), - }, - 1000, - mock_runtime.contracts[&U256::from(1 as u64)].code.clone(), - Transaction { - origin: U256::from(0 as u64), - gas_price: U256::from(1 as u64), - }, - U256::from(1 as u64), - ); - let result = context.execute(&mut mock_runtime); - assert_eq!(result, true); - assert_eq!(*mock_runtime.storage(U256::from(1 as u64)).get(&(U256::from_str(#result_address).unwrap())).unwrap(), U256::from_str(#result_value).unwrap()); - } - }); - } - } - // Combine all generated tests into a single TokenStream +#[proc_macro] +pub fn generate_official_tests(input: TokenStream) -> TokenStream { + println!("Generating"); + let path_name = parse_macro_input!(input as LitStr); + let path_name = path_name.value(); + let tests = generate_tests_from_path(&path_name); let expanded = quote! { #(#tests)* }; - TokenStream::from(expanded) } +fn generate_tests_from_path(path: &str) -> Vec { + match fs::metadata(path) { + Ok(metadata) => { + if metadata.is_file() { -#[proc_macro] -pub fn generate_official_tests_from_folder(input: TokenStream) -> TokenStream { - let folder_name_lit = parse_macro_input!(input as LitStr); - let folder_name = folder_name_lit.value(); - let mut tests = Vec::new(); - for entry in fs::read_dir(folder_name).unwrap() { - let entry = entry.unwrap().path(); - let file_name = String::from(entry.to_str().unwrap()); - let test_name = entry.file_stem().unwrap().to_str().unwrap(); - - // println!("File: {}", file_name); - // Read the content of the specified JSON file - let input_str = match std::fs::read_to_string(&file_name) { - Ok(content) => content, - Err(err) => { - // Handle file reading error here - panic!("Failed to read file {}: {:?}", file_name, err); - } - }; - - let json_data: HashMap = serde_json::from_str(&input_str).unwrap(); - let mut num_tests: usize = 0; - match json_data[test_name].clone() { - Value::Object(obj) => match obj["post"].clone() { - Value::Object(post) => match post["Berlin"].clone() { - Value::Object(berlin) => { - println!("Berlin: {:?}", berlin); - } - - Value::Array(arr) => { - num_tests = arr.len(); - } - _ => { - panic!("Expected a JSON object at the root"); - } - }, - _ => { - panic!("Expected a JSON object at the root"); + return generate_tests_from_file(path); + } else if metadata.is_dir() { + let mut tests = Vec::new(); + for entry in fs::read_dir(path).unwrap() { + tests.append(&mut generate_tests_from_path( + entry.unwrap().path().to_str().unwrap(), + )); } - }, - _ => { - panic!("Expected a JSON object at the root"); + return tests; } } + Err(_) => println!("Failed to get metadata for {}", path), + } + return vec![]; +} - for i in 0..num_tests { - let test_name = Ident::new( - format!("run_test_{}_{}_{}",folder_name_lit.value().replace("/", "_").replace(".", ""), test_name.replace("+", "pos").replace("-", "min"), i).as_str(), - proc_macro2::Span::call_site(), - ); - tests.push(quote! { - #[test] - fn #test_name() { - let filename = #file_name; - run_test_file(filename.to_string(), true, #i); - } - }); - } +fn generate_tests_from_file(file_path: &str) -> Vec { + let mut test_functions = vec![]; + let parsed_tests: BTreeMap = + serde_json::from_reader(BufReader::new(File::open(file_path).unwrap())).unwrap(); + let clean_file_path = clean_path(file_path); + for (index, test) in parsed_tests.iter().nth(0).unwrap().1.tests().iter().enumerate() { + let test_name = Ident::new( + &(clean_file_path.clone() + &index.to_string()), + proc_macro2::Span::call_site(), + ); + let test_str = serde_json::to_string(test).unwrap(); + let test_str = LitStr::new(&test_str, proc_macro2::Span::call_site()); + test_functions.push(quote! { + #[test] + fn #test_name() { + let test_string = #test_str; + // let test_string: str = #test_str; + let test: TestState = serde_json::from_str(test_string).unwrap(); + // run_test(test, true); + } + }); } - // Combine all generated tests into a single TokenStream - let expanded = quote! { - #(#tests)* - }; + test_functions +} - TokenStream::from(expanded) - // TokenStream::from(quote!{}) +fn clean_path(path: &str) -> String { + path.replace("/", "_") + .replace(".", "") + .replace("-", "_sub_") + .replace("+", "_add_") } -#[proc_macro] -pub fn generate_official_tests_from_file(input: TokenStream) -> TokenStream { - let file_name_lit = parse_macro_input!(input as LitStr); - let file_name = file_name_lit.value(); - let mut tests = Vec::new(); - // let entry = entry.unwrap().path(); - // let test_name = entry.file_stem().unwrap().to_str().unwrap(); - - // println!("File: {}", file_name); - // Read the content of the specified JSON file - let input_str = match std::fs::read_to_string(&file_name) { - Ok(content) => content, - Err(err) => { - // Handle file reading error here - panic!("Failed to read file {}: {:?}", file_name, err); - } - }; - - let json_data: HashMap = serde_json::from_str(&input_str).unwrap(); - let mut num_tests: usize = 0; - let test_name = Path::new(&file_name) - .file_stem() - .and_then(|s| s.to_str()) - .unwrap_or("invalid_file_name"); - match json_data[test_name].clone() { - Value::Object(obj) => match obj["post"].clone() { - Value::Object(post) => match post["Berlin"].clone() { - Value::Object(berlin) => { - println!("Berlin: {:?}", berlin); - } - - Value::Array(arr) => { - num_tests = arr.len(); - } - _ => { - panic!("Expected a JSON object at the root"); - } - }, - _ => { - panic!("Expected a JSON object at the root"); - } - }, - _ => { - panic!("Expected a JSON object at the root"); - } - } - for i in 0..num_tests { - // for i in 43..44 { - let test_name = Ident::new( - format!("run_test_{}", i).as_str(), - proc_macro2::Span::call_site(), - ); - tests.push(quote! { - #[test] - fn #test_name() { - let filename = #file_name; - run_test_file(filename.to_string(), true, #i as usize); - } - }); - } +// #[derive(Serialize, Deserialize, Debug)] +// struct BasicTestObject { +// name: String, +// code: String, +// result_address: String, +// result_value: String, +// } - // Combine all generated tests into a single TokenStream - let expanded = quote! { - #(#tests)* - }; +// #[proc_macro] +// pub fn generate_tests(input: TokenStream) -> TokenStream { +// let folder_name_lit = parse_macro_input!(input as LitStr); +// let folder_name = folder_name_lit.value(); +// let mut tests = Vec::new(); +// for entry in fs::read_dir(folder_name).unwrap() { +// let entry = entry.unwrap(); +// let file_name = String::from(entry.path().to_str().unwrap()); +// // Read the content of the specified JSON file +// let input_str = match std::fs::read_to_string(&file_name) { +// Ok(content) => content, +// Err(err) => { +// // Handle file reading error here +// panic!("Failed to read file {}: {:?}", file_name, err); +// } +// }; - TokenStream::from(expanded) - // TokenStream::from(quote!{}) -} +// let input: Vec = match serde_json::from_str(&input_str) { +// Ok(obj) => obj, +// Err(err) => { +// // Handle parsing error here +// panic!("Failed to parse input from file {}: {:?}", file_name, err); +// } +// }; + +// for (_, test) in input.into_iter().enumerate() { +// let test_name = Ident::new(test.name.as_str(), proc_macro2::Span::call_site()); +// let code = test.code.as_str(); +// let result_address = test.result_address; +// let result_value = test.result_value; +// tests.push(quote! { +// #[test] +// fn #test_name() { + +// let code = assemble(String::from(#code)); +// let contract = Contract { +// balance: U256::from(10 as u64), +// code_size: U256::from(code.len() as u64), +// code_hash: util::keccak256(&code), +// code: code, +// nonce: U256::from(0 as u64), +// storage: BTreeMap::new(), +// is_deleted: false, +// is_cold: false, +// hot_keys: HashSet::new(), +// }; +// let mut contracts: BTreeMap = BTreeMap::new(); +// contracts.insert(U256::from(1 as u64), contract); +// let mut mock_runtime = MockRuntime { +// block_hashes: BTreeMap::new(), +// block_number: U256::from(0 as u64), +// block_coinbase: U256::from(0 as u64), +// block_timestamp: U256::from(0 as u64), +// block_difficulty: U256::from(0 as u64), +// block_randomness: U256::from(0 as u64), +// block_gas_limit: U256::from(100000 as u64), +// block_base_fee_per_gas: U256::from(1 as u64), +// chain_id: U256::from(0 as u64), +// contracts: contracts, +// }; + +// let mut context = EVMContext::create_sub_context( +// U256::from(1 as u64), +// Message { +// caller: U256::from(0 as u64), +// value: U256::from(10 as u64), +// data: Memory::new(), +// }, +// 1000, +// mock_runtime.contracts[&U256::from(1 as u64)].code.clone(), +// Transaction { +// origin: U256::from(0 as u64), +// gas_price: U256::from(1 as u64), +// }, +// U256::from(1 as u64), +// ); +// let result = context.execute(&mut mock_runtime); +// assert_eq!(result, true); +// assert_eq!(*mock_runtime.storage(U256::from(1 as u64)).get(&(U256::from_str(#result_address).unwrap())).unwrap(), U256::from_str(#result_value).unwrap()); +// } +// }); +// } +// } +// // Combine all generated tests into a single TokenStream +// let expanded = quote! { +// #(#tests)* +// }; + +// TokenStream::from(expanded) +// } + +// #[proc_macro] +// pub fn generate_official_tests_from_folder(input: TokenStream) -> TokenStream { +// let folder_name_lit = parse_macro_input!(input as LitStr); +// let folder_name = folder_name_lit.value(); +// let mut tests = Vec::new(); +// for entry in fs::read_dir(folder_name).unwrap() { +// let entry = entry.unwrap().path(); +// let file_name = String::from(entry.to_str().unwrap()); +// let test_name = entry.file_stem().unwrap().to_str().unwrap(); + +// // println!("File: {}", file_name); +// // Read the content of the specified JSON file +// let input_str = match std::fs::read_to_string(&file_name) { +// Ok(content) => content, +// Err(err) => { +// // Handle file reading error here +// panic!("Failed to read file {}: {:?}", file_name, err); +// } +// }; + +// let json_data: HashMap = serde_json::from_str(&input_str).unwrap(); +// let mut num_tests: usize = 0; +// match json_data[test_name].clone() { +// Value::Object(obj) => match obj["post"].clone() { +// Value::Object(post) => match post["Berlin"].clone() { +// Value::Object(berlin) => { +// println!("Berlin: {:?}", berlin); +// } + +// Value::Array(arr) => { +// num_tests = arr.len(); +// } +// _ => { +// panic!("Expected a JSON object at the root"); +// } +// }, +// _ => { +// panic!("Expected a JSON object at the root"); +// } +// }, +// _ => { +// panic!("Expected a JSON object at the root"); +// } +// } + +// for i in 0..num_tests { +// let test_name = Ident::new( +// format!( +// "run_test_{}_{}_{}", +// folder_name_lit.value().replace("/", "_").replace(".", ""), +// test_name.replace("+", "pos").replace("-", "min"), +// i +// ) +// .as_str(), +// proc_macro2::Span::call_site(), +// ); +// tests.push(quote! { +// #[test] +// fn #test_name() { +// let filename = #file_name; +// run_test_file(filename.to_string(), true, #i); +// } +// }); +// } +// } +// // Combine all generated tests into a single TokenStream +// let expanded = quote! { +// #(#tests)* +// }; + +// TokenStream::from(expanded) +// // TokenStream::from(quote!{}) +// } + +// #[proc_macro] +// pub fn generate_official_tests_from_file(input: TokenStream) -> TokenStream { +// let file_name_lit = parse_macro_input!(input as LitStr); +// let file_name = file_name_lit.value(); +// let mut tests = Vec::new(); +// // let entry = entry.unwrap().path(); +// // let test_name = entry.file_stem().unwrap().to_str().unwrap(); + +// // println!("File: {}", file_name); +// // Read the content of the specified JSON file +// let input_str = match std::fs::read_to_string(&file_name) { +// Ok(content) => content, +// Err(err) => { +// // Handle file reading error here +// panic!("Failed to read file {}: {:?}", file_name, err); +// } +// }; + +// let json_data: HashMap = serde_json::from_str(&input_str).unwrap(); +// let mut num_tests: usize = 0; +// let test_name = Path::new(&file_name) +// .file_stem() +// .and_then(|s| s.to_str()) +// .unwrap_or("invalid_file_name"); +// match json_data[test_name].clone() { +// Value::Object(obj) => match obj["post"].clone() { +// Value::Object(post) => match post["Berlin"].clone() { +// Value::Object(berlin) => { +// println!("Berlin: {:?}", berlin); +// } + +// Value::Array(arr) => { +// num_tests = arr.len(); +// } +// _ => { +// panic!("Expected a JSON object at the root"); +// } +// }, +// _ => { +// panic!("Expected a JSON object at the root"); +// } +// }, +// _ => { +// panic!("Expected a JSON object at the root"); +// } +// } +// for i in 0..num_tests { +// // for i in 43..44 { +// let test_name = Ident::new( +// format!("run_test_{}", i).as_str(), +// proc_macro2::Span::call_site(), +// ); +// tests.push(quote! { +// #[test] +// fn #test_name() { +// let filename = #file_name; +// run_test_file(filename.to_string(), true, #i as usize); +// } +// }); +// } + +// // Combine all generated tests into a single TokenStream +// let expanded = quote! { +// #(#tests)* +// }; + +// TokenStream::from(expanded) +// // TokenStream::from(quote!{}) +// } diff --git a/tests/official_tests/official_tests.rs b/tests/official_tests/official_tests.rs index f0a610c..a8f1d2a 100644 --- a/tests/official_tests/official_tests.rs +++ b/tests/official_tests/official_tests.rs @@ -4,19 +4,27 @@ use ethereum_evm::{ execute_transaction, result::ExecutionResult, - runtime::Runtime, util::{keccak256, u256_to_h256}, + runtime::Runtime, + util::{keccak256, u256_to_h256}, }; use primitive_types::U256; +use serde_json::json; +use serde_json::value::Value; use std::{ collections::{BTreeMap, HashSet}, fs::File, io::BufReader, }; -use test_gen::{generate_official_tests_from_file, generate_official_tests_from_folder}; +use test_gen::generate_official_tests; use crate::mocks::mock_runtime::{Contract, MockRuntime}; -use super::types::{TestState, TestStateMulti}; +use official_test_types::types::{TestState, TestStateMulti}; + +generate_official_tests!( + "./tests/official_tests/tests/GeneralStateTests/VMTests" +); +generate_official_tests!("./tests/official_tests/tests/GeneralStateTests/stMemoryTest"); pub fn run_test_file(filename: String, debug: bool, index: usize) { let tests: BTreeMap = @@ -65,7 +73,7 @@ pub fn run_test(test: &TestState, debug: bool) { hot_keys: HashSet::new(), }, ); - println!("Storage: {:?}",contract.storage().clone()); + println!("Storage: {:?}", contract.storage().clone()); } contracts.insert( test.env.current_coinbase, @@ -114,17 +122,20 @@ pub fn run_test(test: &TestState, debug: bool) { runtime.deposit(test.transaction.to, test.transaction.value); // withdraw the value from the sender runtime.withdrawal(test.transaction.sender, test.transaction.value); - }, + } _ => {} } // withdraw the gas usage from the sender runtime.withdrawal(test.transaction.sender, U256::from(eth_usage as u64)); runtime.deposit(test.env.current_coinbase, U256::from(eth_usage as u64)); runtime.merge_context(); - println!("Context {:?}", match runtime.current_context { - Some(_) => "Exists", - _ => "Doesn't Exist", - }); + println!( + "Context {:?}", + match runtime.current_context { + Some(_) => "Exists", + _ => "Doesn't Exist", + } + ); for (address, contract) in &runtime.contracts { println!("Address: {:x}", address); println!("Storage: {:?}", contract.storage); @@ -145,19 +156,18 @@ pub fn run_test(test: &TestState, debug: bool) { // "./tests/official_tests/tests/GeneralStateTests/VMTests/vmPerformance" // ); - // generate_official_tests_from_file!( // "./tests/official_tests/tests/GeneralStateTests/stMemoryTest/mem32kb-33.json" // ); // generate_official_tests_from_file!( // "./tests/official_tests/tests/GeneralStateTests/VMTests/vmArithmeticTest/fib.json" // ); -generate_official_tests_from_folder!( - "./tests/official_tests/tests/GeneralStateTests/VMTests/vmArithmeticTest" -); -generate_official_tests_from_file!( - "./tests/official_tests/tests/GeneralStateTests/stMemoryTest/buffer.json" -); -generate_official_tests_from_folder!( - "./tests/official_tests/tests/GeneralStateTests/VMTests/vmBitwiseLogicOperation" -); +// generate_official_tests_from_folder!( +// "./tests/official_tests/tests/GeneralStateTests/VMTests/vmArithmeticTest" +// ); +// generate_official_tests_from_file!( +// "./tests/official_tests/tests/GeneralStateTests/stMemoryTest/buffer.json" +// ); +// generate_official_tests_from_folder!( +// "./tests/official_tests/tests/GeneralStateTests/VMTests/vmBitwiseLogicOperation" +// ); diff --git a/tests/official_tests/types.rs b/tests/official_tests/types.rs index da86cb7..aaa8cd4 100644 --- a/tests/official_tests/types.rs +++ b/tests/official_tests/types.rs @@ -1,212 +1,212 @@ -use ethereum_evm::util::{h256_to_u256, u256_to_h256}; -use hex::FromHex; -use primitive_types::{H256, U256}; -use serde::{Deserialize, Deserializer}; -use std::{collections::BTreeMap, fmt}; - -use super::util::Hex; - -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] -struct WrappedU256(U256); - -impl<'de> Deserialize<'de> for WrappedU256 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct H256Visitor; - - impl<'de> serde::de::Visitor<'de> for H256Visitor { - type Value = WrappedU256; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a string representing a hex-encoded H256 value") - } - - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - let value_without_prefix = if value.starts_with("0x") { - &value[2..] // Skip the first two characters (0x) - } else { - value - }; - let hash_bytes: Vec = match Vec::::from_hex(value_without_prefix) { - Ok(bytes) => bytes, - Err(_) => return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(value), &self)), - }; - - let mut hash = [0u8; 32]; - let num_bytes_to_copy = hash_bytes.len().min(32); - // println!("num_bytes_to_copy: {}", num_bytes_to_copy); - // println!("hash_bytes: {:x?}", hash_bytes); - hash[32 - num_bytes_to_copy..32].copy_from_slice(&hash_bytes); - // println!("hash: {:x?}", hash); - Ok(WrappedU256(U256::from(hash))) - } - } - - deserializer.deserialize_str(H256Visitor) - } -} - - -#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TestStateMulti { - #[serde(rename = "_info")] - pub info: TestInfo, - pub env: TestEnv, - pub post: BTreeMap>, - pub pre: BTreeMap, - pub transaction: TestTransactionMulti, -} - -impl TestStateMulti { - pub fn tests(&self) -> Vec { - let mut tests = Vec::new(); - for (fork, post_states) in &self.post { - if fork == "Berlin" { - let mut new_tests: Vec = post_states - .iter() - .enumerate() - .map(|(index, post_state)| TestState { - info: self.info.clone(), - env: self.env.clone(), - fork: fork.clone(), - post: post_state.clone(), - pre: self.pre.clone(), - transaction: TestTransaction { - data: self.transaction.data[post_state.indexes.data].0.clone(), - gas_limit: self.transaction.gas_limit[post_state.indexes.gas], - gas_price: self.transaction.gas_price, - max_fee_per_gas: self.transaction.max_fee_per_gas, - max_priority_fee_per_gas: self.transaction.max_priority_fee_per_gas, - nonce: self.transaction.nonce, - secret_key: self.transaction.secret_key, - sender: self.transaction.sender, - to: self.transaction.to, - value: self.transaction.value[post_state.indexes.value], - }, - }) - .collect(); - tests.append(&mut new_tests); - } - } - tests - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TestState { - #[serde(rename = "_info")] - pub info: TestInfo, - pub env: TestEnv, - pub fork: String, - pub post: TestPost, - pub pre: BTreeMap, - pub transaction: TestTransaction, -} - -#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TestInfo { - pub comment: String, - #[serde(rename = "filling-rpc-server")] - pub filling_rpc_server: String, - #[serde(rename = "filling-tool-version")] - pub filling_tool_version: String, - pub labels: Option>, - pub generated_test_hash: String, - #[serde(rename = "lllcversion")] - pub lllc_version: String, - pub solidity: String, - pub source: String, - pub source_hash: String, -} - -#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TestEnv { - pub current_base_fee: U256, - pub current_coinbase: U256, - pub current_difficulty: U256, - pub current_excess_blob_gas: U256, - pub current_gas_limit: U256, - pub current_number: U256, - pub current_random: U256, - pub current_timestamp: U256, -} - -#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TestPost { - pub hash: H256, - pub indexes: TestPostIndexes, - pub logs: U256, - #[serde(rename = "txbytes")] - pub tx_bytes: Hex, -} - -#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TestPostIndexes { - pub data: usize, - pub gas: usize, - pub value: usize, -} - -#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TestContract { - balance: WrappedU256, - pub code: Hex, - nonce: WrappedU256, - storage: BTreeMap, -} - -impl TestContract{ - pub fn storage(&self) -> BTreeMap { - self.storage.iter().map(|(k, v)| (u256_to_h256(k.0), u256_to_h256(v.0))).collect() - } - - pub fn nonce(&self) -> U256 { - self.nonce.0 - } - - pub fn balance(&self) -> U256 { - self.balance.0 - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TestTransactionMulti { - pub data: Vec, - pub gas_limit: Vec, - pub gas_price: Option, - pub max_fee_per_gas: Option, - pub max_priority_fee_per_gas: Option, - pub nonce: U256, - pub secret_key: U256, - pub to: U256, - pub sender: U256, - pub value: Vec, -} - -#[derive(Clone, Debug, Eq, PartialEq, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TestTransaction { - pub data: Vec, - pub gas_limit: U256, - pub gas_price: Option, - pub max_fee_per_gas: Option, - pub max_priority_fee_per_gas: Option, - pub nonce: U256, - pub secret_key: U256, - pub sender: U256, - pub to: U256, - pub value: U256, -} +// use ethereum_evm::util::{h256_to_u256, u256_to_h256}; +// use hex::FromHex; +// use primitive_types::{H256, U256}; +// use serde::{Deserialize, Deserializer}; +// use std::{collections::BTreeMap, fmt}; + +// use super::util::Hex; + +// #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +// struct WrappedU256(U256); + +// impl<'de> Deserialize<'de> for WrappedU256 { +// fn deserialize(deserializer: D) -> Result +// where +// D: Deserializer<'de>, +// { +// struct H256Visitor; + +// impl<'de> serde::de::Visitor<'de> for H256Visitor { +// type Value = WrappedU256; + +// fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { +// formatter.write_str("a string representing a hex-encoded H256 value") +// } + +// fn visit_str(self, value: &str) -> Result +// where +// E: serde::de::Error, +// { +// let value_without_prefix = if value.starts_with("0x") { +// &value[2..] // Skip the first two characters (0x) +// } else { +// value +// }; +// let hash_bytes: Vec = match Vec::::from_hex(value_without_prefix) { +// Ok(bytes) => bytes, +// Err(_) => return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(value), &self)), +// }; + +// let mut hash = [0u8; 32]; +// let num_bytes_to_copy = hash_bytes.len().min(32); +// // println!("num_bytes_to_copy: {}", num_bytes_to_copy); +// // println!("hash_bytes: {:x?}", hash_bytes); +// hash[32 - num_bytes_to_copy..32].copy_from_slice(&hash_bytes); +// // println!("hash: {:x?}", hash); +// Ok(WrappedU256(U256::from(hash))) +// } +// } + +// deserializer.deserialize_str(H256Visitor) +// } +// } + + +// #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +// #[serde(rename_all = "camelCase")] +// pub struct TestStateMulti { +// #[serde(rename = "_info")] +// pub info: TestInfo, +// pub env: TestEnv, +// pub post: BTreeMap>, +// pub pre: BTreeMap, +// pub transaction: TestTransactionMulti, +// } + +// impl TestStateMulti { +// pub fn tests(&self) -> Vec { +// let mut tests = Vec::new(); +// for (fork, post_states) in &self.post { +// if fork == "Berlin" { +// let mut new_tests: Vec = post_states +// .iter() +// .enumerate() +// .map(|(index, post_state)| TestState { +// info: self.info.clone(), +// env: self.env.clone(), +// fork: fork.clone(), +// post: post_state.clone(), +// pre: self.pre.clone(), +// transaction: TestTransaction { +// data: self.transaction.data[post_state.indexes.data].0.clone(), +// gas_limit: self.transaction.gas_limit[post_state.indexes.gas], +// gas_price: self.transaction.gas_price, +// max_fee_per_gas: self.transaction.max_fee_per_gas, +// max_priority_fee_per_gas: self.transaction.max_priority_fee_per_gas, +// nonce: self.transaction.nonce, +// secret_key: self.transaction.secret_key, +// sender: self.transaction.sender, +// to: self.transaction.to, +// value: self.transaction.value[post_state.indexes.value], +// }, +// }) +// .collect(); +// tests.append(&mut new_tests); +// } +// } +// tests +// } +// } + +// #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +// #[serde(rename_all = "camelCase")] +// pub struct TestState { +// #[serde(rename = "_info")] +// pub info: TestInfo, +// pub env: TestEnv, +// pub fork: String, +// pub post: TestPost, +// pub pre: BTreeMap, +// pub transaction: TestTransaction, +// } + +// #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +// #[serde(rename_all = "camelCase")] +// pub struct TestInfo { +// pub comment: String, +// #[serde(rename = "filling-rpc-server")] +// pub filling_rpc_server: String, +// #[serde(rename = "filling-tool-version")] +// pub filling_tool_version: String, +// pub labels: Option>, +// pub generated_test_hash: String, +// #[serde(rename = "lllcversion")] +// pub lllc_version: String, +// pub solidity: String, +// pub source: String, +// pub source_hash: String, +// } + +// #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +// #[serde(rename_all = "camelCase")] +// pub struct TestEnv { +// pub current_base_fee: U256, +// pub current_coinbase: U256, +// pub current_difficulty: U256, +// pub current_excess_blob_gas: U256, +// pub current_gas_limit: U256, +// pub current_number: U256, +// pub current_random: U256, +// pub current_timestamp: U256, +// } + +// #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +// #[serde(rename_all = "camelCase")] +// pub struct TestPost { +// pub hash: H256, +// pub indexes: TestPostIndexes, +// pub logs: U256, +// #[serde(rename = "txbytes")] +// pub tx_bytes: Hex, +// } + +// #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +// #[serde(rename_all = "camelCase")] +// pub struct TestPostIndexes { +// pub data: usize, +// pub gas: usize, +// pub value: usize, +// } + +// #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +// #[serde(rename_all = "camelCase")] +// pub struct TestContract { +// balance: WrappedU256, +// pub code: Hex, +// nonce: WrappedU256, +// storage: BTreeMap, +// } + +// impl TestContract{ +// pub fn storage(&self) -> BTreeMap { +// self.storage.iter().map(|(k, v)| (u256_to_h256(k.0), u256_to_h256(v.0))).collect() +// } + +// pub fn nonce(&self) -> U256 { +// self.nonce.0 +// } + +// pub fn balance(&self) -> U256 { +// self.balance.0 +// } +// } + +// #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +// #[serde(rename_all = "camelCase")] +// pub struct TestTransactionMulti { +// pub data: Vec, +// pub gas_limit: Vec, +// pub gas_price: Option, +// pub max_fee_per_gas: Option, +// pub max_priority_fee_per_gas: Option, +// pub nonce: U256, +// pub secret_key: U256, +// pub to: U256, +// pub sender: U256, +// pub value: Vec, +// } + +// #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] +// #[serde(rename_all = "camelCase")] +// pub struct TestTransaction { +// pub data: Vec, +// pub gas_limit: U256, +// pub gas_price: Option, +// pub max_fee_per_gas: Option, +// pub max_priority_fee_per_gas: Option, +// pub nonce: U256, +// pub secret_key: U256, +// pub sender: U256, +// pub to: U256, +// pub value: U256, +// } diff --git a/tests/tests_from_definitions.rs b/tests/tests_from_definitions.rs index 10bf3c2..e08af7e 100644 --- a/tests/tests_from_definitions.rs +++ b/tests/tests_from_definitions.rs @@ -1,9 +1,9 @@ mod mocks; -use std::{collections::BTreeMap, str::FromStr, collections::HashSet}; -// use ethereum_evm::{assembler::assemble, evm::{EVMContext, Message, Transaction}, runtime::Runtime, state::memory::Memory, util}; -use mocks::mock_runtime::{Contract, MockRuntime}; -use test_gen::generate_tests; mod official_tests; -use official_tests::official_tests::run_test_file; -// Basic tests using only a single contract, (no gas checks) -// generate_tests!("./tests/test_definitions/basic_tests.json"); +// use std::{collections::BTreeMap, str::FromStr, collections::HashSet}; +// // use ethereum_evm::{assembler::assemble, evm::{EVMContext, Message, Transaction}, runtime::Runtime, state::memory::Memory, util}; +// use mocks::mock_runtime::{Contract, MockRuntime}; +// // use test_gen::generate_tests; +// use official_tests::official_tests::run_test_file; +// // Basic tests using only a single contract, (no gas checks) +// // generate_tests!("./tests/test_definitions/basic_tests.json");