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.
This commit is contained in:
Omar Abdulla
2025-07-29 10:51:41 +03:00
parent 3045468742
commit 68be320645
16 changed files with 204 additions and 58 deletions
Generated
+4
View File
@@ -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",
+1
View File
@@ -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 }
+1
View File
@@ -4,3 +4,4 @@
pub mod concepts;
pub mod iterators;
pub mod macros;
pub mod types;
+3
View File
@@ -0,0 +1,3 @@
mod version_or_requirement;
pub use version_or_requirement::*;
@@ -0,0 +1,41 @@
use semver::{Version, VersionReq};
#[derive(Clone, Debug)]
pub enum VersionOrRequirement {
Version(Version),
Requirement(VersionReq),
}
impl From<Version> for VersionOrRequirement {
fn from(value: Version) -> Self {
Self::Version(value)
}
}
impl From<VersionReq> for VersionOrRequirement {
fn from(value: VersionReq) -> Self {
Self::Requirement(value)
}
}
impl TryFrom<VersionOrRequirement> for Version {
type Error = anyhow::Error;
fn try_from(value: VersionOrRequirement) -> Result<Self, Self::Error> {
let VersionOrRequirement::Version(version) = value else {
anyhow::bail!("Version or requirement was not a version");
};
Ok(version)
}
}
impl TryFrom<VersionOrRequirement> for VersionReq {
type Error = anyhow::Error;
fn try_from(value: VersionOrRequirement) -> Result<Self, Self::Error> {
let VersionOrRequirement::Requirement(requirement) = value else {
anyhow::bail!("Version or requirement was not a requirement");
};
Ok(requirement)
}
}
+1
View File
@@ -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 }
+3 -1
View File
@@ -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<VersionOrRequirement>,
) -> anyhow::Result<PathBuf>;
fn version(&self) -> anyhow::Result<Version>;
}
/// The generic compilation input configuration.
+47 -2
View File
@@ -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<semver::Version> {
// 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");
}
}
+52 -1
View File
@@ -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<semver::Version> {
// 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)
)
}
}
+9 -1
View File
@@ -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<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
let default = Arguments::parse_from(["retester"]);
Arguments {
temp_dir: Some(&TEMP_DIR),
..default
}
}
}
+1
View File
@@ -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 }
+25 -11
View File
@@ -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<ContractInstance, (Address, JsonAbi)>,
/// Stores the version of the compiler used for the given Solc mode.
compiler_version: HashMap<&'a SolcMode, Version>,
phantom: PhantomData<T>,
}
@@ -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::<T::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(());
}
}
+10
View File
@@ -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 {
+2
View File
@@ -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 }
+3 -41
View File
@@ -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<Version> for VersionOrRequirement {
fn from(value: Version) -> Self {
Self::Version(value)
}
}
impl From<VersionReq> for VersionOrRequirement {
fn from(value: VersionReq) -> Self {
Self::Requirement(value)
}
}
impl TryFrom<VersionOrRequirement> for Version {
type Error = anyhow::Error;
fn try_from(value: VersionOrRequirement) -> Result<Self, Self::Error> {
let VersionOrRequirement::Version(version) = value else {
anyhow::bail!("Version or requirement was not a version");
};
Ok(version)
}
}
impl TryFrom<VersionOrRequirement> for VersionReq {
type Error = anyhow::Error;
fn try_from(value: VersionOrRequirement) -> Result<Self, Self::Error> {
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};
+1 -1
View File
@@ -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;