From ca5b2fe15e0b9920ede13f1c67918b93bfdc12c2 Mon Sep 17 00:00:00 2001 From: Roy <42067944+royvardhan@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:21:45 +0530 Subject: [PATCH] feat: extract statetest models/structs to standalone crate (#1808) * feat: split test models to statetest-tests * chore: attempt documentation * Update Cargo.toml * Update Cargo.toml --------- --- Cargo.lock | 10 + Cargo.toml | 2 + bins/revme/Cargo.toml | 1 + bins/revme/src/cmd/statetest.rs | 1 - bins/revme/src/cmd/statetest/models/mod.rs | 198 ------------------ bins/revme/src/cmd/statetest/runner.rs | 3 +- crates/statetest-types/CHANGELOG.md | 0 crates/statetest-types/Cargo.toml | 28 +++ crates/statetest-types/LICENSE | 21 ++ crates/statetest-types/src/account_info.rs | 15 ++ .../statetest-types/src}/deserializer.rs | 2 + crates/statetest-types/src/env.rs | 24 +++ crates/statetest-types/src/lib.rs | 27 +++ .../statetest-types/src}/spec.rs | 2 + crates/statetest-types/src/test.rs | 28 +++ .../statetest-types/src/test_authorization.rs | 44 ++++ crates/statetest-types/src/test_suite.rs | 8 + crates/statetest-types/src/test_unit.rs | 21 ++ crates/statetest-types/src/transaction.rs | 42 ++++ 19 files changed, 277 insertions(+), 200 deletions(-) delete mode 100644 bins/revme/src/cmd/statetest/models/mod.rs create mode 100644 crates/statetest-types/CHANGELOG.md create mode 100644 crates/statetest-types/Cargo.toml create mode 100644 crates/statetest-types/LICENSE create mode 100644 crates/statetest-types/src/account_info.rs rename {bins/revme/src/cmd/statetest/models => crates/statetest-types/src}/deserializer.rs (89%) create mode 100644 crates/statetest-types/src/env.rs create mode 100644 crates/statetest-types/src/lib.rs rename {bins/revme/src/cmd/statetest/models => crates/statetest-types/src}/spec.rs (96%) create mode 100644 crates/statetest-types/src/test.rs create mode 100644 crates/statetest-types/src/test_authorization.rs create mode 100644 crates/statetest-types/src/test_suite.rs create mode 100644 crates/statetest-types/src/test_unit.rs create mode 100644 crates/statetest-types/src/transaction.rs diff --git a/Cargo.lock b/Cargo.lock index 47702b3bb1..9d6c5b241b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3061,6 +3061,15 @@ dependencies = [ "serde", ] +[[package]] +name = "revm-statetest-types" +version = "1.0.0" +dependencies = [ + "revm", + "serde", + "serde_json", +] + [[package]] name = "revm-wiring" version = "1.0.0" @@ -3096,6 +3105,7 @@ dependencies = [ "revm-bytecode", "revm-database", "revm-inspector", + "revm-statetest-types", "serde", "serde_json", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index d3adb3e832..63c0ed6bb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ members = [ "crates/state", "crates/wiring", "crates/specification", + "crates/statetest-types", # examples "examples/block_traces", @@ -42,6 +43,7 @@ revm = { path = "crates/revm", version = "14.0.1", default-features = false } interpreter = { path = "crates/interpreter", package = "revm-interpreter", version = "10.0.1", default-features = false } inspector = { path = "crates/inspector", package = "revm-inspector", version = "1.0.0", default-features = false } precompile = { path = "crates/precompile", package = "revm-precompile", version = "11.0.1", default-features = false } +statetest-types = { path = "crates/statetest-types", package = "revm-statetest-types", version = "1.0.0", default-features = false } [workspace.package] license = "MIT" diff --git a/bins/revme/Cargo.toml b/bins/revme/Cargo.toml index 072996142f..2e46be070b 100644 --- a/bins/revme/Cargo.toml +++ b/bins/revme/Cargo.toml @@ -12,6 +12,7 @@ repository.workspace = true # revm database.workspace = true revm = { workspace = true, features = ["std", "hashbrown", "c-kzg", "blst"] } +statetest-types = { workspace = true } inspector = { workspace = true, features = ["std", "serde-json"] } # enable parse std and parse feature. bytecode = { workspace = true, features = ["std", "parse"] } diff --git a/bins/revme/src/cmd/statetest.rs b/bins/revme/src/cmd/statetest.rs index 2b62a43430..a14419ebd4 100644 --- a/bins/revme/src/cmd/statetest.rs +++ b/bins/revme/src/cmd/statetest.rs @@ -1,5 +1,4 @@ pub mod merkle_trie; -pub mod models; mod runner; pub mod utils; diff --git a/bins/revme/src/cmd/statetest/models/mod.rs b/bins/revme/src/cmd/statetest/models/mod.rs deleted file mode 100644 index d3c46ec898..0000000000 --- a/bins/revme/src/cmd/statetest/models/mod.rs +++ /dev/null @@ -1,198 +0,0 @@ -mod deserializer; -mod spec; - -use deserializer::*; - -pub use spec::SpecName; - -use revm::{ - primitives::{Address, Bytes, HashMap, B256, U256}, - specification::{ - eip2930::AccessList, - eip7702::{Authorization, Parity, RecoveredAuthorization, Signature}, - }, -}; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; - -#[derive(Debug, PartialEq, Eq, Deserialize)] -pub struct TestSuite(pub BTreeMap); - -#[derive(Debug, PartialEq, Eq, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct TestUnit { - /// Test info is optional - #[serde(default, rename = "_info")] - pub info: Option, - - pub env: Env, - pub pre: HashMap, - pub post: BTreeMap>, - pub transaction: TransactionParts, - #[serde(default)] - pub out: Option, -} - -/// State test indexed state result deserialization. -#[derive(Debug, PartialEq, Eq, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct Test { - pub expect_exception: Option, - - /// Indexes - pub indexes: TxPartIndices, - /// Post state hash - pub hash: B256, - /// Post state - #[serde(default)] - pub post_state: HashMap, - - /// Logs root - pub logs: B256, - - /// Tx bytes - pub txbytes: Option, -} - -#[derive(Debug, PartialEq, Eq, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct TxPartIndices { - pub data: usize, - pub gas: usize, - pub value: usize, -} - -#[derive(Clone, Debug, PartialEq, Eq, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct AccountInfo { - pub balance: U256, - pub code: Bytes, - #[serde(deserialize_with = "deserialize_str_as_u64")] - pub nonce: u64, - pub storage: HashMap, -} - -#[derive(Debug, PartialEq, Eq, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct Env { - pub current_coinbase: Address, - #[serde(default)] - pub current_difficulty: U256, - pub current_gas_limit: U256, - pub current_number: U256, - pub current_timestamp: U256, - pub current_base_fee: Option, - pub previous_hash: Option, - - pub current_random: Option, - pub current_beacon_root: Option, - pub current_withdrawals_root: Option, - - pub parent_blob_gas_used: Option, - pub parent_excess_blob_gas: Option, - pub current_excess_blob_gas: Option, -} - -#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TransactionParts { - pub data: Vec, - pub gas_limit: Vec, - pub gas_price: Option, - pub nonce: U256, - pub secret_key: B256, - /// if sender is not present we need to derive it from secret key. - #[serde(default)] - pub sender: Option
, - #[serde(default, deserialize_with = "deserialize_maybe_empty")] - pub to: Option
, - pub value: Vec, - pub max_fee_per_gas: Option, - pub max_priority_fee_per_gas: Option, - - #[serde(default)] - pub access_lists: Vec>, - pub authorization_list: Option>, - #[serde(default)] - pub blob_versioned_hashes: Vec, - pub max_fee_per_blob_gas: Option, -} - -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct TestAuthorization { - chain_id: U256, - address: Address, - nonce: U256, - v: U256, - r: U256, - s: U256, - signer: Option
, -} - -impl TestAuthorization { - pub fn signature(&self) -> Signature { - let v = u64::try_from(self.v).unwrap_or(u64::MAX); - let parity = Parity::try_from(v).unwrap_or(Parity::Eip155(36)); - Signature::from_rs_and_parity(self.r, self.s, parity).unwrap() - } - - pub fn into_recovered(self) -> RecoveredAuthorization { - let authorization = Authorization { - chain_id: self.chain_id, - address: self.address, - nonce: u64::try_from(self.nonce).unwrap(), - }; - let authority = self - .signature() - .recover_address_from_prehash(&authorization.signature_hash()) - .ok(); - RecoveredAuthorization::new_unchecked( - authorization.into_signed(self.signature()), - authority, - ) - } -} - -#[cfg(test)] -mod tests { - - use super::*; - use serde_json::Error; - - #[test] - pub fn serialize_u256() -> Result<(), Error> { - let json = r#"{"_item":"0x10"}"#; - - #[derive(Deserialize, Debug)] - pub struct Test { - _item: Option, - } - - let out: Test = serde_json::from_str(json)?; - println!("out:{out:?}"); - Ok(()) - } - - #[test] - pub fn deserialize_minimal_transaction_parts() -> Result<(), Error> { - let json = r#"{"data":[],"gasLimit":[],"nonce":"0x0","secretKey":"0x0000000000000000000000000000000000000000000000000000000000000000","to":"","value":[]}"#; - - let _: TransactionParts = serde_json::from_str(json)?; - Ok(()) - } - - #[test] - pub fn serialize_b160() -> Result<(), Error> { - let json = r#"{"_item":"0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"}"#; - - #[derive(Deserialize, Debug)] - pub struct Test { - _item: Address, - } - - let out: Test = serde_json::from_str(json)?; - println!("out:{out:?}"); - Ok(()) - } -} diff --git a/bins/revme/src/cmd/statetest/runner.rs b/bins/revme/src/cmd/statetest/runner.rs index 1496e57ac9..4de0628a5c 100644 --- a/bins/revme/src/cmd/statetest/runner.rs +++ b/bins/revme/src/cmd/statetest/runner.rs @@ -1,6 +1,5 @@ use super::{ merkle_trie::{log_rlp_hash, state_merkle_trie_root}, - models::{SpecName, Test, TestSuite}, utils::recover_address, }; use database::State; @@ -20,6 +19,8 @@ use revm::{ Evm, }; use serde_json::json; +use statetest_types::{SpecName, Test, TestSuite}; + use std::{ fmt::Debug, io::{stderr, stdout}, diff --git a/crates/statetest-types/CHANGELOG.md b/crates/statetest-types/CHANGELOG.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/statetest-types/Cargo.toml b/crates/statetest-types/Cargo.toml new file mode 100644 index 0000000000..c41e490a74 --- /dev/null +++ b/crates/statetest-types/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "revm-statetest-types" +description = "State test types for revm" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +keywords.workspace = true +license.workspace = true +repository.workspace = true +readme.workspace = true + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[lints.rust] +unreachable_pub = "warn" +unused_must_use = "deny" +rust_2018_idioms = "deny" + +[lints.rustdoc] +all = "warn" + +[dependencies] +# revm +revm = { workspace = true, features = ["std", "serde"] } +serde = { version = "1.0", features = ["derive", "rc"] } +serde_json = { version = "1.0", features = ["preserve_order"] } diff --git a/crates/statetest-types/LICENSE b/crates/statetest-types/LICENSE new file mode 100644 index 0000000000..ad98ff22cc --- /dev/null +++ b/crates/statetest-types/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-2024 draganrakita + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/statetest-types/src/account_info.rs b/crates/statetest-types/src/account_info.rs new file mode 100644 index 0000000000..9ed9f78a0d --- /dev/null +++ b/crates/statetest-types/src/account_info.rs @@ -0,0 +1,15 @@ +use revm::primitives::{Bytes, HashMap, U256}; +use serde::Deserialize; + +use crate::deserializer::deserialize_str_as_u64; + +/// Account information. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct AccountInfo { + pub balance: U256, + pub code: Bytes, + #[serde(deserialize_with = "deserialize_str_as_u64")] + pub nonce: u64, + pub storage: HashMap, +} diff --git a/bins/revme/src/cmd/statetest/models/deserializer.rs b/crates/statetest-types/src/deserializer.rs similarity index 89% rename from bins/revme/src/cmd/statetest/models/deserializer.rs rename to crates/statetest-types/src/deserializer.rs index a89c70a99c..622b3d6ce3 100644 --- a/bins/revme/src/cmd/statetest/models/deserializer.rs +++ b/crates/statetest-types/src/deserializer.rs @@ -1,6 +1,7 @@ use revm::primitives::Address; use serde::{de, Deserialize}; +/// Deserialize a string as a u64. pub fn deserialize_str_as_u64<'de, D>(deserializer: D) -> Result where D: de::Deserializer<'de>, @@ -15,6 +16,7 @@ where .map_err(serde::de::Error::custom) } +/// Deserialize a string as an optional Address. pub fn deserialize_maybe_empty<'de, D>(deserializer: D) -> Result, D::Error> where D: de::Deserializer<'de>, diff --git a/crates/statetest-types/src/env.rs b/crates/statetest-types/src/env.rs new file mode 100644 index 0000000000..b510357f19 --- /dev/null +++ b/crates/statetest-types/src/env.rs @@ -0,0 +1,24 @@ +use revm::primitives::{Address, B256, U256}; +use serde::Deserialize; + +/// Environment variables. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct Env { + pub current_coinbase: Address, + #[serde(default)] + pub current_difficulty: U256, + pub current_gas_limit: U256, + pub current_number: U256, + pub current_timestamp: U256, + pub current_base_fee: Option, + pub previous_hash: Option, + + pub current_random: Option, + pub current_beacon_root: Option, + pub current_withdrawals_root: Option, + + pub parent_blob_gas_used: Option, + pub parent_excess_blob_gas: Option, + pub current_excess_blob_gas: Option, +} diff --git a/crates/statetest-types/src/lib.rs b/crates/statetest-types/src/lib.rs new file mode 100644 index 0000000000..578df9555c --- /dev/null +++ b/crates/statetest-types/src/lib.rs @@ -0,0 +1,27 @@ +//! # revm-statetest-types +//! +//! This crate provides type definitions and utilities for Ethereum state tests, +//! specifically tailored for use with REVM. +//! +//! It includes structures for representing account information, environment settings, +//! test cases, and transaction data used in Ethereum state tests. + +mod account_info; +mod deserializer; +mod env; +mod spec; +mod test; +mod test_authorization; +mod test_suite; +mod test_unit; +mod transaction; + +pub use account_info::*; +pub use deserializer::*; +pub use env::*; +pub use spec::*; +pub use test::*; +pub use test_authorization::*; +pub use test_suite::*; +pub use test_unit::*; +pub use transaction::*; diff --git a/bins/revme/src/cmd/statetest/models/spec.rs b/crates/statetest-types/src/spec.rs similarity index 96% rename from bins/revme/src/cmd/statetest/models/spec.rs rename to crates/statetest-types/src/spec.rs index bd3fea9dba..fd24c54167 100644 --- a/bins/revme/src/cmd/statetest/models/spec.rs +++ b/crates/statetest-types/src/spec.rs @@ -1,6 +1,7 @@ use revm::specification::hardfork::SpecId; use serde::Deserialize; +/// Ethereum specification names. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Hash)] pub enum SpecName { Frontier, @@ -31,6 +32,7 @@ pub enum SpecName { } impl SpecName { + /// Convert to a spec id. pub fn to_spec_id(&self) -> SpecId { match self { Self::Frontier => SpecId::FRONTIER, diff --git a/crates/statetest-types/src/test.rs b/crates/statetest-types/src/test.rs new file mode 100644 index 0000000000..8a59f0dd0d --- /dev/null +++ b/crates/statetest-types/src/test.rs @@ -0,0 +1,28 @@ +use revm::{ + primitives::{Address, Bytes, HashMap, B256}, + state::AccountInfo, +}; +use serde::Deserialize; + +use crate::transaction::TxPartIndices; + +/// State test indexed state result deserialization. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct Test { + pub expect_exception: Option, + + /// Indexes + pub indexes: TxPartIndices, + /// Post state hash + pub hash: B256, + /// Post state + #[serde(default)] + pub post_state: HashMap, + + /// Logs root + pub logs: B256, + + /// Tx bytes + pub txbytes: Option, +} diff --git a/crates/statetest-types/src/test_authorization.rs b/crates/statetest-types/src/test_authorization.rs new file mode 100644 index 0000000000..c003503ebb --- /dev/null +++ b/crates/statetest-types/src/test_authorization.rs @@ -0,0 +1,44 @@ +use revm::{ + primitives::{Address, U256}, + specification::eip7702::{Authorization, Parity, RecoveredAuthorization, Signature}, +}; +use serde::{Deserialize, Serialize}; + +/// Test authorization. +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct TestAuthorization { + chain_id: U256, + address: Address, + nonce: U256, + v: U256, + r: U256, + s: U256, + signer: Option
, +} + +impl TestAuthorization { + /// Get the signature using the v, r, s values. + pub fn signature(&self) -> Signature { + let v = u64::try_from(self.v).unwrap_or(u64::MAX); + let parity = Parity::try_from(v).unwrap_or(Parity::Eip155(36)); + Signature::from_rs_and_parity(self.r, self.s, parity).unwrap() + } + + /// Convert to a recovered authorization. + pub fn into_recovered(self) -> RecoveredAuthorization { + let authorization = Authorization { + chain_id: self.chain_id, + address: self.address, + nonce: u64::try_from(self.nonce).unwrap(), + }; + let authority = self + .signature() + .recover_address_from_prehash(&authorization.signature_hash()) + .ok(); + RecoveredAuthorization::new_unchecked( + authorization.into_signed(self.signature()), + authority, + ) + } +} diff --git a/crates/statetest-types/src/test_suite.rs b/crates/statetest-types/src/test_suite.rs new file mode 100644 index 0000000000..79c3d72da1 --- /dev/null +++ b/crates/statetest-types/src/test_suite.rs @@ -0,0 +1,8 @@ +use serde::Deserialize; +use std::collections::BTreeMap; + +use crate::TestUnit; + +/// The top level test suite. +#[derive(Debug, PartialEq, Eq, Deserialize)] +pub struct TestSuite(pub BTreeMap); diff --git a/crates/statetest-types/src/test_unit.rs b/crates/statetest-types/src/test_unit.rs new file mode 100644 index 0000000000..76309258c4 --- /dev/null +++ b/crates/statetest-types/src/test_unit.rs @@ -0,0 +1,21 @@ +use serde::Deserialize; +use std::collections::{BTreeMap, HashMap}; + +use crate::{AccountInfo, Env, SpecName, Test, TransactionParts}; +use revm::primitives::{Address, Bytes}; + +/// A single test unit. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct TestUnit { + /// Test info is optional + #[serde(default, rename = "_info")] + pub info: Option, + + pub env: Env, + pub pre: HashMap, + pub post: BTreeMap>, + pub transaction: TransactionParts, + #[serde(default)] + pub out: Option, +} diff --git a/crates/statetest-types/src/transaction.rs b/crates/statetest-types/src/transaction.rs new file mode 100644 index 0000000000..f68e041a7a --- /dev/null +++ b/crates/statetest-types/src/transaction.rs @@ -0,0 +1,42 @@ +use revm::{ + primitives::{Address, Bytes, B256, U256}, + specification::eip2930::AccessList, +}; +use serde::{Deserialize, Serialize}; + +use crate::{deserializer::deserialize_maybe_empty, TestAuthorization}; + +/// Transaction parts. +#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionParts { + pub data: Vec, + pub gas_limit: Vec, + pub gas_price: Option, + pub nonce: U256, + pub secret_key: B256, + /// if sender is not present we need to derive it from secret key. + #[serde(default)] + pub sender: Option
, + #[serde(default, deserialize_with = "deserialize_maybe_empty")] + pub to: Option
, + pub value: Vec, + pub max_fee_per_gas: Option, + pub max_priority_fee_per_gas: Option, + + #[serde(default)] + pub access_lists: Vec>, + pub authorization_list: Option>, + #[serde(default)] + pub blob_versioned_hashes: Vec, + pub max_fee_per_blob_gas: Option, +} + +/// Transaction part indices. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct TxPartIndices { + pub data: usize, + pub gas: usize, + pub value: usize, +}