diff --git a/Cargo.lock b/Cargo.lock index 02d801f..3d855c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4095,6 +4095,7 @@ dependencies = [ "alloy-primitives", "alloy-sol-types", "anyhow", + "revive-common", "revive-dt-common", "semver 1.0.26", "serde", @@ -4109,6 +4110,7 @@ version = "0.1.0" dependencies = [ "alloy", "anyhow", + "revive-common", "revive-dt-common", "revive-dt-config", "revive-dt-format", diff --git a/Cargo.toml b/Cargo.toml index 92bb064..2441471 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ authors = ["Parity Technologies "] license = "MIT/Apache-2.0" edition = "2024" repository = "https://github.com/paritytech/revive-differential-testing.git" -rust-version = "1.85.0" +rust-version = "1.87.0" [workspace.dependencies] revive-dt-common = { version = "0.1.0", path = "crates/common" } diff --git a/crates/core/src/driver/mod.rs b/crates/core/src/driver/mod.rs index ae96a08..435bd39 100644 --- a/crates/core/src/driver/mod.rs +++ b/crates/core/src/driver/mod.rs @@ -550,6 +550,7 @@ where BalanceAssertion { address: address_string, expected_balance: amount, + .. }: &BalanceAssertion, node: &T::Blockchain, ) -> anyhow::Result<()> { @@ -609,6 +610,7 @@ where StorageEmptyAssertion { address: address_string, is_storage_empty, + .. }: &StorageEmptyAssertion, node: &T::Blockchain, ) -> anyhow::Result<()> { diff --git a/crates/core/src/main.rs b/crates/core/src/main.rs index 7336542..81a688f 100644 --- a/crates/core/src/main.rs +++ b/crates/core/src/main.rs @@ -177,6 +177,27 @@ where Some(false) | None => true, }, ) + .filter(|(metadata_file_path, metadata, ..)| match metadata.required_evm_version { + Some(evm_version_requirement) => { + let is_allowed = evm_version_requirement + .matches(&::evm_version()) + && evm_version_requirement + .matches(&::evm_version()); + + if !is_allowed { + tracing::warn!( + metadata_file_path = %metadata_file_path.display(), + leader_evm_version = %::evm_version(), + follower_evm_version = %::evm_version(), + version_requirement = %evm_version_requirement, + "Skipped test since the EVM version requirement was not fulfilled." + ); + } + + is_allowed + } + None => true, + }) .collect::>(); let metadata_case_status = Arc::new(RwLock::new(test_cases.iter().fold( diff --git a/crates/format/Cargo.toml b/crates/format/Cargo.toml index 48d754b..0f50758 100644 --- a/crates/format/Cargo.toml +++ b/crates/format/Cargo.toml @@ -11,6 +11,8 @@ rust-version.workspace = true [dependencies] revive-dt-common = { workspace = true } +revive-common = { workspace = true } + alloy = { workspace = true } alloy-primitives = { workspace = true } alloy-sol-types = { workspace = true } diff --git a/crates/format/src/metadata.rs b/crates/format/src/metadata.rs index 99debe3..ad4fac9 100644 --- a/crates/format/src/metadata.rs +++ b/crates/format/src/metadata.rs @@ -1,4 +1,5 @@ use std::{ + cmp::Ordering, collections::BTreeMap, fmt::Display, fs::{File, read_to_string}, @@ -9,6 +10,7 @@ use std::{ use serde::{Deserialize, Serialize}; +use revive_common::EVMVersion; use revive_dt_common::{iterators::FilesWithExtensionIterator, macros::define_wrapper_type}; use crate::{ @@ -58,6 +60,12 @@ pub struct Metadata { pub modes: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub file_path: Option, + + /// This field specifies an EVM version requirement that the test case has + /// where the test might be run of the evm version of the nodes match the + /// evm version specified here. + #[serde(skip_serializing_if = "Option::is_none")] + pub required_evm_version: Option, } impl Metadata { @@ -348,6 +356,131 @@ impl From for String { } } +/// An EVM version requirement that the test case has. This gets serialized and +/// deserialized from and into [`String`]. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[serde(try_from = "String", into = "String")] +pub struct EvmVersionRequirement { + ordering: Ordering, + or_equal: bool, + evm_version: EVMVersion, +} + +impl EvmVersionRequirement { + pub fn new_greater_than_or_equals(version: EVMVersion) -> Self { + Self { + ordering: Ordering::Greater, + or_equal: true, + evm_version: version, + } + } + + pub fn new_greater_than(version: EVMVersion) -> Self { + Self { + ordering: Ordering::Greater, + or_equal: false, + evm_version: version, + } + } + + pub fn new_equals(version: EVMVersion) -> Self { + Self { + ordering: Ordering::Equal, + or_equal: false, + evm_version: version, + } + } + + pub fn new_less_than(version: EVMVersion) -> Self { + Self { + ordering: Ordering::Less, + or_equal: false, + evm_version: version, + } + } + + pub fn new_less_than_or_equals(version: EVMVersion) -> Self { + Self { + ordering: Ordering::Less, + or_equal: true, + evm_version: version, + } + } + + pub fn matches(&self, other: &EVMVersion) -> bool { + let ordering = other.cmp(&self.evm_version); + ordering == self.ordering || (self.or_equal && matches!(ordering, Ordering::Equal)) + } +} + +impl Display for EvmVersionRequirement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + ordering, + or_equal, + evm_version, + } = self; + match ordering { + Ordering::Less => write!(f, "<")?, + Ordering::Equal => write!(f, "=")?, + Ordering::Greater => write!(f, ">")?, + } + if *or_equal && !matches!(ordering, Ordering::Equal) { + write!(f, "=")?; + } + write!(f, "{evm_version}") + } +} + +impl FromStr for EvmVersionRequirement { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s.as_bytes() { + [b'>', b'=', remaining @ ..] => Ok(Self { + ordering: Ordering::Greater, + or_equal: true, + evm_version: str::from_utf8(remaining)?.try_into()?, + }), + [b'>', remaining @ ..] => Ok(Self { + ordering: Ordering::Greater, + or_equal: false, + evm_version: str::from_utf8(remaining)?.try_into()?, + }), + [b'<', b'=', remaining @ ..] => Ok(Self { + ordering: Ordering::Less, + or_equal: true, + evm_version: str::from_utf8(remaining)?.try_into()?, + }), + [b'<', remaining @ ..] => Ok(Self { + ordering: Ordering::Less, + or_equal: false, + evm_version: str::from_utf8(remaining)?.try_into()?, + }), + [b'=', remaining @ ..] => Ok(Self { + ordering: Ordering::Equal, + or_equal: false, + evm_version: str::from_utf8(remaining)?.try_into()?, + }), + _ => anyhow::bail!("Invalid EVM version requirement {s}"), + } + } +} + +impl TryFrom for EvmVersionRequirement { + type Error = anyhow::Error; + + fn try_from(value: String) -> Result { + value.parse() + } +} + +impl From for String { + fn from(value: EvmVersionRequirement) -> Self { + value.to_string() + } +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index a930312..318e1a2 100644 --- a/crates/node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -14,6 +14,7 @@ alloy = { workspace = true } tracing = { workspace = true } tokio = { workspace = true } +revive-common = { workspace = true } revive-dt-common = { workspace = true } revive-dt-config = { workspace = true } revive-dt-format = { workspace = true } diff --git a/crates/node/src/geth.rs b/crates/node/src/geth.rs index 38fba17..9d91040 100644 --- a/crates/node/src/geth.rs +++ b/crates/node/src/geth.rs @@ -32,6 +32,7 @@ use alloy::{ signers::local::PrivateKeySigner, }; use anyhow::Context; +use revive_common::EVMVersion; use tracing::{Instrument, Level}; use revive_dt_common::{fs::clear_directory, futures::poll}; @@ -579,6 +580,10 @@ impl Node for GethNode { Some(targets) => targets.iter().any(|str| str.as_str() == "evm"), } } + + fn evm_version() -> EVMVersion { + EVMVersion::Cancun + } } impl Drop for GethNode { diff --git a/crates/node/src/kitchensink.rs b/crates/node/src/kitchensink.rs index 4ef398c..c5dd254 100644 --- a/crates/node/src/kitchensink.rs +++ b/crates/node/src/kitchensink.rs @@ -32,6 +32,7 @@ use alloy::{ signers::local::PrivateKeySigner, }; use anyhow::Context; +use revive_common::EVMVersion; use revive_dt_common::fs::clear_directory; use revive_dt_format::traits::ResolverApi; use serde::{Deserialize, Serialize}; @@ -638,6 +639,10 @@ impl Node for KitchensinkNode { Some(targets) => targets.iter().any(|str| str.as_str() == "pvm"), } } + + fn evm_version() -> EVMVersion { + EVMVersion::Cancun + } } impl Drop for KitchensinkNode { diff --git a/crates/node/src/lib.rs b/crates/node/src/lib.rs index 1232e97..446b66a 100644 --- a/crates/node/src/lib.rs +++ b/crates/node/src/lib.rs @@ -1,5 +1,6 @@ //! This crate implements the testing nodes. +use revive_common::EVMVersion; use revive_dt_config::Arguments; use revive_dt_node_interaction::EthereumNode; @@ -36,4 +37,7 @@ pub trait Node: EthereumNode { /// Given a list of targets from the metadata file, this function determines if the metadata /// file can be ran on this node or not. fn matches_target(&self, targets: Option<&[String]>) -> bool; + + /// Returns the EVM version of the node. + fn evm_version() -> EVMVersion; }