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;