From 68be3206452f18804d94174370824c999fb860b3 Mon Sep 17 00:00:00 2001 From: Omar Abdulla Date: Tue, 29 Jul 2025 10:51:41 +0300 Subject: [PATCH] Honor the compiler version requirement in metadata This commit honors the compiler version requirement listed in the solc modes of the metadata file. If this version requirement is provided then it overrides what was passed in the CLI. Otherwise, the CLI version will be used. --- Cargo.lock | 4 ++ crates/common/Cargo.toml | 1 + crates/common/src/lib.rs | 1 + crates/common/src/types/mod.rs | 3 ++ .../src/types/version_or_requirement.rs | 41 ++++++++++++++ crates/compiler/Cargo.toml | 1 + crates/compiler/src/lib.rs | 4 +- crates/compiler/src/revive_resolc.rs | 49 ++++++++++++++++- crates/compiler/src/solc.rs | 53 ++++++++++++++++++- crates/config/src/lib.rs | 10 +++- crates/core/Cargo.toml | 1 + crates/core/src/driver/mod.rs | 36 +++++++++---- crates/format/src/mode.rs | 10 ++++ crates/solc-binaries/Cargo.toml | 2 + crates/solc-binaries/src/download.rs | 44 ++------------- crates/solc-binaries/src/lib.rs | 2 +- 16 files changed, 204 insertions(+), 58 deletions(-) create mode 100644 crates/common/src/types/mod.rs create mode 100644 crates/common/src/types/version_or_requirement.rs diff --git a/Cargo.lock b/Cargo.lock index 20507b9..3e2f201 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3955,6 +3955,7 @@ dependencies = [ "anyhow", "futures", "once_cell", + "semver 1.0.26", "tokio", "tracing", ] @@ -3966,6 +3967,7 @@ dependencies = [ "alloy-primitives", "anyhow", "revive-common", + "revive-dt-common", "revive-dt-config", "revive-dt-solc-binaries", "revive-solc-json-interface", @@ -4002,6 +4004,7 @@ dependencies = [ "revive-dt-node-interaction", "revive-dt-report", "revive-solc-json-interface", + "semver 1.0.26", "serde_json", "temp-dir", "tracing", @@ -4070,6 +4073,7 @@ dependencies = [ "anyhow", "hex", "reqwest", + "revive-dt-common", "semver 1.0.26", "serde", "sha2 0.10.9", diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 50d3d5e..7dc81c4 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -11,6 +11,7 @@ rust-version.workspace = true [dependencies] anyhow = { workspace = true } futures = { workspace = true } +semver = { workspace = true } tracing = { workspace = true } once_cell = { workspace = true } tokio = { workspace = true } diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index baee9f1..8b0ae35 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -4,3 +4,4 @@ pub mod concepts; pub mod iterators; pub mod macros; +pub mod types; diff --git a/crates/common/src/types/mod.rs b/crates/common/src/types/mod.rs new file mode 100644 index 0000000..4cd063a --- /dev/null +++ b/crates/common/src/types/mod.rs @@ -0,0 +1,3 @@ +mod version_or_requirement; + +pub use version_or_requirement::*; diff --git a/crates/common/src/types/version_or_requirement.rs b/crates/common/src/types/version_or_requirement.rs new file mode 100644 index 0000000..787a37d --- /dev/null +++ b/crates/common/src/types/version_or_requirement.rs @@ -0,0 +1,41 @@ +use semver::{Version, VersionReq}; + +#[derive(Clone, Debug)] +pub enum VersionOrRequirement { + Version(Version), + Requirement(VersionReq), +} + +impl From for VersionOrRequirement { + fn from(value: Version) -> Self { + Self::Version(value) + } +} + +impl From for VersionOrRequirement { + fn from(value: VersionReq) -> Self { + Self::Requirement(value) + } +} + +impl TryFrom for Version { + type Error = anyhow::Error; + + fn try_from(value: VersionOrRequirement) -> Result { + let VersionOrRequirement::Version(version) = value else { + anyhow::bail!("Version or requirement was not a version"); + }; + Ok(version) + } +} + +impl TryFrom for VersionReq { + type Error = anyhow::Error; + + fn try_from(value: VersionOrRequirement) -> Result { + let VersionOrRequirement::Requirement(requirement) = value else { + anyhow::bail!("Version or requirement was not a requirement"); + }; + Ok(requirement) + } +} diff --git a/crates/compiler/Cargo.toml b/crates/compiler/Cargo.toml index 05b02d4..4a7d671 100644 --- a/crates/compiler/Cargo.toml +++ b/crates/compiler/Cargo.toml @@ -10,6 +10,7 @@ rust-version.workspace = true [dependencies] revive-solc-json-interface = { workspace = true } +revive-dt-common = { workspace = true } revive-dt-config = { workspace = true } revive-dt-solc-binaries = { workspace = true } revive-common = { workspace = true } diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs index ab2eb2a..cb192b9 100644 --- a/crates/compiler/src/lib.rs +++ b/crates/compiler/src/lib.rs @@ -13,7 +13,7 @@ use alloy_primitives::Address; use revive_dt_config::Arguments; use revive_common::EVMVersion; -use revive_dt_solc_binaries::download::VersionOrRequirement; +use revive_dt_common::types::VersionOrRequirement; use revive_solc_json_interface::{ SolcStandardJsonInput, SolcStandardJsonInputLanguage, SolcStandardJsonInputSettings, SolcStandardJsonInputSettingsOptimizer, SolcStandardJsonInputSettingsSelection, @@ -42,6 +42,8 @@ pub trait SolidityCompiler { config: &Arguments, version: impl Into, ) -> anyhow::Result; + + fn version(&self) -> anyhow::Result; } /// The generic compilation input configuration. diff --git a/crates/compiler/src/revive_resolc.rs b/crates/compiler/src/revive_resolc.rs index dd405c5..c272ff4 100644 --- a/crates/compiler/src/revive_resolc.rs +++ b/crates/compiler/src/revive_resolc.rs @@ -6,11 +6,15 @@ use std::{ process::{Command, Stdio}, }; -use crate::{CompilerInput, CompilerOutput, SolidityCompiler}; +use revive_dt_common::types::VersionOrRequirement; use revive_dt_config::Arguments; -use revive_dt_solc_binaries::download::VersionOrRequirement; use revive_solc_json_interface::SolcStandardJsonOutput; +use crate::{CompilerInput, CompilerOutput, SolidityCompiler}; + +use anyhow::Context; +use semver::Version; + // TODO: I believe that we need to also pass the solc compiler to resolc so that resolc uses the // specified solc compiler. I believe that currently we completely ignore the specified solc binary // when invoking resolc which doesn't seem right if we're using solc as a compiler frontend. @@ -156,4 +160,45 @@ impl SolidityCompiler for Resolc { Ok(PathBuf::from("resolc")) } + + fn version(&self) -> anyhow::Result { + // Logic for parsing the resolc version from the following string: + // Solidity frontend for the revive compiler version 0.3.0+commit.b238913.llvm-18.1.8 + + let output = Command::new(self.resolc_path.as_path()) + .arg("--version") + .stdout(Stdio::piped()) + .spawn()? + .wait_with_output()? + .stdout; + let output = String::from_utf8_lossy(&output); + let version_string = output + .split("version ") + .nth(1) + .context("Version parsing failed")? + .split("+") + .next() + .context("Version parsing failed")?; + + Version::parse(version_string).map_err(Into::into) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn compiler_version_can_be_obtained() { + // Arrange + let args = Arguments::default(); + let path = Resolc::get_compiler_executable(&args, Version::new(0, 7, 6)).unwrap(); + let compiler = Resolc::new(path); + + // Act + let version = compiler.version(); + + // Assert + let _ = version.expect("Failed to get version"); + } } diff --git a/crates/compiler/src/solc.rs b/crates/compiler/src/solc.rs index 2d6fb83..cc72247 100644 --- a/crates/compiler/src/solc.rs +++ b/crates/compiler/src/solc.rs @@ -7,9 +7,12 @@ use std::{ }; use crate::{CompilerInput, CompilerOutput, SolidityCompiler}; +use anyhow::Context; +use revive_dt_common::types::VersionOrRequirement; use revive_dt_config::Arguments; -use revive_dt_solc_binaries::{download::VersionOrRequirement, download_solc}; +use revive_dt_solc_binaries::download_solc; use revive_solc_json_interface::SolcStandardJsonOutput; +use semver::Version; #[derive(Debug)] pub struct Solc { @@ -100,4 +103,52 @@ impl SolidityCompiler for Solc { let path = download_solc(config.directory(), version, config.wasm)?; Ok(path) } + + fn version(&self) -> anyhow::Result { + // The following is the parsing code for the version from the solc version strings which + // look like the following: + // ``` + // solc, the solidity compiler commandline interface + // Version: 0.8.30+commit.73712a01.Darwin.appleclang + // ``` + + let child = Command::new(self.solc_path.as_path()) + .arg("--version") + .stdout(Stdio::piped()) + .spawn()?; + let output = child.wait_with_output()?; + let output = String::from_utf8_lossy(&output.stdout); + let version_line = output + .split("Version: ") + .nth(1) + .context("Version parsing failed")?; + let version_string = version_line + .split("+") + .next() + .context("Version parsing failed")?; + + Version::parse(version_string).map_err(Into::into) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn compiler_version_can_be_obtained() { + // Arrange + let args = Arguments::default(); + let path = Solc::get_compiler_executable(&args, Version::new(0, 7, 6)).unwrap(); + let compiler = Solc::new(path); + + // Act + let version = compiler.version(); + + // Assert + assert_eq!( + version.expect("Failed to get version"), + Version::new(0, 7, 6) + ) + } } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 7a78bc2..0d5fde1 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -3,6 +3,7 @@ use std::{ fmt::Display, path::{Path, PathBuf}, + sync::LazyLock, }; use alloy::{network::EthereumWallet, signers::local::PrivateKeySigner}; @@ -144,7 +145,14 @@ impl Arguments { impl Default for Arguments { fn default() -> Self { - Arguments::parse_from(["retester"]) + static TEMP_DIR: LazyLock = LazyLock::new(|| TempDir::new().unwrap()); + + let default = Arguments::parse_from(["retester"]); + + Arguments { + temp_dir: Some(&TEMP_DIR), + ..default + } } } diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 7c6c551..4aad88c 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -30,4 +30,5 @@ tracing-subscriber = { workspace = true } rayon = { workspace = true } revive-solc-json-interface = { workspace = true } serde_json = { workspace = true } +semver = { workspace = true } temp-dir = { workspace = true } diff --git a/crates/core/src/driver/mod.rs b/crates/core/src/driver/mod.rs index efd99c4..2bfa330 100644 --- a/crates/core/src/driver/mod.rs +++ b/crates/core/src/driver/mod.rs @@ -21,6 +21,7 @@ use alloy::{ }; use anyhow::Context; use indexmap::IndexMap; +use semver::Version; use serde_json::Value; use revive_dt_common::iterators::FilesWithExtensionIterator; @@ -64,6 +65,9 @@ pub struct State<'a, T: Platform> { /// the libraries with each case. deployed_libraries: HashMap, + /// Stores the version of the compiler used for the given Solc mode. + compiler_version: HashMap<&'a SolcMode, Version>, + phantom: PhantomData, } @@ -78,6 +82,7 @@ where contracts: Default::default(), deployed_contracts: Default::default(), deployed_libraries: Default::default(), + compiler_version: Default::default(), phantom: Default::default(), } } @@ -87,7 +92,11 @@ where self.span } - pub fn build_contracts(&mut self, mode: &SolcMode, metadata: &Metadata) -> anyhow::Result<()> { + pub fn build_contracts( + &mut self, + mode: &'a SolcMode, + metadata: &Metadata, + ) -> anyhow::Result<()> { let mut span = self.span(); span.next_metadata( metadata @@ -97,9 +106,14 @@ where .clone(), ); - let Some(version) = mode.last_patch_version(&self.config.solc) else { - anyhow::bail!("unsupported solc version: {:?}", &mode.solc_version); - }; + let compiler_version_or_requirement = + mode.compiler_version_to_use(self.config.solc.clone()); + let compiler_path = + T::Compiler::get_compiler_executable(self.config, compiler_version_or_requirement)?; + let compiler_version = T::Compiler::new(compiler_path.clone()).version()?; + self.compiler_version.insert(mode, compiler_version.clone()); + + tracing::info!(%compiler_version, "Resolved the compiler version to use"); let compiler = Compiler::::new() .allow_path(metadata.directory()?) @@ -131,11 +145,10 @@ where json_input: compiler.input(), json_output: None, mode: mode.clone(), - compiler_version: format!("{}", &version), + compiler_version: format!("{}", &compiler_version), error: None, }; - let compiler_path = T::Compiler::get_compiler_executable(self.config, version)?; match compiler.try_build(compiler_path) { Ok(output) => { task.json_output = Some(output.output.clone()); @@ -170,7 +183,7 @@ where pub fn build_and_publish_libraries( &mut self, metadata: &Metadata, - mode: &SolcMode, + mode: &'a SolcMode, node: &T::Blockchain, ) -> anyhow::Result<()> { self.build_contracts(mode, metadata)?; @@ -387,10 +400,11 @@ where mode: &SolcMode, ) -> anyhow::Result<()> { if let Some(ref version_requirement) = expectation.compiler_version { - let Some(compiler_version) = mode.last_patch_version(&self.config.solc) else { - anyhow::bail!("unsupported solc version: {:?}", &mode.solc_version); - }; - if !version_requirement.matches(&compiler_version) { + let compiler_version = self + .compiler_version + .get(mode) + .context("Failed to find the compiler version fo the solc mode")?; + if !version_requirement.matches(compiler_version) { return Ok(()); } } diff --git a/crates/format/src/mode.rs b/crates/format/src/mode.rs index a99a5e8..69b55b2 100644 --- a/crates/format/src/mode.rs +++ b/crates/format/src/mode.rs @@ -1,3 +1,4 @@ +use revive_dt_common::types::VersionOrRequirement; use semver::Version; use serde::de::Deserializer; use serde::{Deserialize, Serialize}; @@ -78,6 +79,15 @@ impl SolcMode { None } + + /// Resolves the [`SolcMode`]'s solidity version requirement into a [`VersionOrRequirement`] if + /// the requirement is present on the object. Otherwise, the passed default version is used. + pub fn compiler_version_to_use(&self, default: Version) -> VersionOrRequirement { + match self.solc_version { + Some(ref requirement) => requirement.clone().into(), + None => default.into(), + } + } } impl<'de> Deserialize<'de> for Mode { diff --git a/crates/solc-binaries/Cargo.toml b/crates/solc-binaries/Cargo.toml index 22d29b0..9bb6090 100644 --- a/crates/solc-binaries/Cargo.toml +++ b/crates/solc-binaries/Cargo.toml @@ -9,6 +9,8 @@ repository.workspace = true rust-version.workspace = true [dependencies] +revive-dt-common = { workspace = true } + anyhow = { workspace = true } hex = { workspace = true } tracing = { workspace = true } diff --git a/crates/solc-binaries/src/download.rs b/crates/solc-binaries/src/download.rs index 4a1cfae..067102c 100644 --- a/crates/solc-binaries/src/download.rs +++ b/crates/solc-binaries/src/download.rs @@ -5,7 +5,9 @@ use std::{ sync::{LazyLock, Mutex}, }; -use semver::{Version, VersionReq}; +use revive_dt_common::types::VersionOrRequirement; + +use semver::Version; use sha2::{Digest, Sha256}; use crate::list::List; @@ -127,46 +129,6 @@ impl GHDownloader { } } -#[derive(Clone, Debug)] -pub enum VersionOrRequirement { - Version(Version), - Requirement(VersionReq), -} - -impl From for VersionOrRequirement { - fn from(value: Version) -> Self { - Self::Version(value) - } -} - -impl From for VersionOrRequirement { - fn from(value: VersionReq) -> Self { - Self::Requirement(value) - } -} - -impl TryFrom for Version { - type Error = anyhow::Error; - - fn try_from(value: VersionOrRequirement) -> Result { - let VersionOrRequirement::Version(version) = value else { - anyhow::bail!("Version or requirement was not a version"); - }; - Ok(version) - } -} - -impl TryFrom for VersionReq { - type Error = anyhow::Error; - - fn try_from(value: VersionOrRequirement) -> Result { - let VersionOrRequirement::Requirement(requirement) = value else { - anyhow::bail!("Version or requirement was not a requirement"); - }; - Ok(requirement) - } -} - #[cfg(test)] mod tests { use crate::{download::GHDownloader, list::List}; diff --git a/crates/solc-binaries/src/lib.rs b/crates/solc-binaries/src/lib.rs index 850fb43..5fefbd8 100644 --- a/crates/solc-binaries/src/lib.rs +++ b/crates/solc-binaries/src/lib.rs @@ -8,7 +8,7 @@ use std::path::{Path, PathBuf}; use cache::get_or_download; use download::GHDownloader; -use crate::download::VersionOrRequirement; +use revive_dt_common::types::VersionOrRequirement; pub mod cache; pub mod download;