From 491a9a32b260a95c85c20e672e69696721afddc7 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Thu, 21 Aug 2025 12:23:54 +0100 Subject: [PATCH] Use the solc version required in tests rather than one on PATH --- Cargo.lock | 1 + .../src/types/version_or_requirement.rs | 87 ++++++++++-- crates/compiler/src/lib.rs | 58 ++++---- crates/compiler/src/revive_resolc.rs | 115 +++++---------- crates/compiler/src/solc.rs | 133 +++--------------- crates/compiler/src/utils.rs | 50 +++++++ crates/compiler/tests/lib.rs | 13 +- crates/core/Cargo.toml | 1 + crates/core/src/cached_compiler.rs | 19 +-- crates/core/src/main.rs | 50 ++----- crates/solc-binaries/src/lib.rs | 43 ++++-- 11 files changed, 263 insertions(+), 307 deletions(-) create mode 100644 crates/compiler/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index a04261a..c6e5e7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4526,6 +4526,7 @@ dependencies = [ "revive-dt-node", "revive-dt-node-interaction", "revive-dt-report", + "revive-dt-solc-binaries", "semver 1.0.26", "serde", "serde_json", diff --git a/crates/common/src/types/version_or_requirement.rs b/crates/common/src/types/version_or_requirement.rs index 787a37d..bb97196 100644 --- a/crates/common/src/types/version_or_requirement.rs +++ b/crates/common/src/types/version_or_requirement.rs @@ -6,6 +6,42 @@ pub enum VersionOrRequirement { Requirement(VersionReq), } +impl VersionOrRequirement { + /// A helper function to convert a [`semver::Version`] into a [`semver::VersionReq`]. + pub fn version_to_requirement(version: &Version) -> VersionReq { + // Ignoring "build" metadata in the version, we can turn + // it into a requirement which is an exact match for the + // given version and nothing else: + VersionReq { + comparators: vec![semver::Comparator { + op: semver::Op::Exact, + major: version.major, + minor: Some(version.minor), + patch: Some(version.patch), + pre: version.pre.clone(), + }], + } + } +} + +impl serde::Serialize for VersionOrRequirement { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + VersionOrRequirement::Version(v) => serializer.serialize_str(&v.to_string()), + VersionOrRequirement::Requirement(r) => serializer.serialize_str(&r.to_string()), + } + } +} + +impl Default for VersionOrRequirement { + fn default() -> Self { + VersionOrRequirement::Requirement(VersionReq::STAR) + } +} + impl From for VersionOrRequirement { fn from(value: Version) -> Self { Self::Version(value) @@ -18,24 +54,45 @@ impl From for VersionOrRequirement { } } +impl From for VersionReq { + fn from(value: VersionOrRequirement) -> Self { + match value { + VersionOrRequirement::Version(version) => { + VersionOrRequirement::version_to_requirement(&version) + } + VersionOrRequirement::Requirement(version_req) => version_req, + } + } +} + 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) + match value { + VersionOrRequirement::Version(version) => Ok(version), + VersionOrRequirement::Requirement(mut version_req) => { + if version_req.comparators.len() != 1 { + anyhow::bail!( + "The version requirement in VersionOrRequirement is not a single exact version" + ); + } + + let c = version_req.comparators.pop().unwrap(); + let (semver::Op::Exact, Some(minor), Some(patch)) = (c.op, c.minor, c.patch) else { + anyhow::bail!( + "The version requirement in VersionOrRequirement is not an exact version" + ); + }; + + Ok(Version { + major: c.major, + minor, + patch, + pre: c.pre, + build: Default::default(), + }) + } + } } } diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs index 05d9868..faec1c2 100644 --- a/crates/compiler/src/lib.rs +++ b/crates/compiler/src/lib.rs @@ -4,6 +4,7 @@ //! - Polkadot revive Wasm compiler mod constants; +mod utils; use std::{ collections::HashMap, @@ -13,12 +14,11 @@ use std::{ use alloy::json_abi::JsonAbi; use alloy_primitives::Address; -use semver::Version; +use semver::VersionReq; use serde::{Deserialize, Serialize}; use revive_common::EVMVersion; use revive_dt_common::cached_fs::read_to_string; -use revive_dt_common::types::VersionOrRequirement; use revive_dt_config::Arguments; // Re-export this as it's a part of the compiler interface. @@ -40,28 +40,20 @@ pub trait SolidityCompiler { additional_options: Self::Options, ) -> impl Future>; - fn new(solc_executable: PathBuf) -> Self; - - fn get_compiler_executable( - config: &Arguments, - version: impl Into, - ) -> impl Future>; - - fn version(&self) -> impl Future>; + /// Instantiate a new compiler. + fn new(config: &Arguments) -> Self; /// Does the compiler support the provided mode and version settings? - fn supports_mode( - compiler_version: &Version, - optimize_setting: ModeOptimizerSetting, - pipeline: ModePipeline, - ) -> bool; + fn supports_mode(optimize_setting: ModeOptimizerSetting, pipeline: ModePipeline) -> bool; } /// The generic compilation input configuration. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] pub struct CompilerInput { + pub wasm: bool, pub pipeline: Option, pub optimization: Option, + pub solc_version: Option, pub evm_version: Option, pub allow_paths: Vec, pub base_path: Option, @@ -84,12 +76,6 @@ pub struct Compiler { additional_options: T::Options, } -impl Default for Compiler { - fn default() -> Self { - Self::new() - } -} - impl Compiler where T: SolidityCompiler, @@ -97,8 +83,10 @@ where pub fn new() -> Self { Self { input: CompilerInput { + wasm: Default::default(), pipeline: Default::default(), optimization: Default::default(), + solc_version: Default::default(), evm_version: Default::default(), allow_paths: Default::default(), base_path: Default::default(), @@ -110,6 +98,16 @@ where } } + pub fn with_wasm(mut self, value: bool) -> Self { + self.input.wasm = value; + self + } + + pub fn with_solc_version_req(mut self, value: impl Into>) -> Self { + self.input.solc_version = value.into(); + self + } + pub fn with_optimization(mut self, value: impl Into>) -> Self { self.input.optimization = value.into(); self @@ -177,11 +175,8 @@ where callback(self) } - pub async fn try_build( - self, - compiler_path: impl AsRef, - ) -> anyhow::Result { - T::new(compiler_path.as_ref().to_path_buf()) + pub async fn try_build(self, config: &Arguments) -> anyhow::Result { + T::new(config) .build(self.input, self.additional_options) .await } @@ -191,6 +186,15 @@ where } } +impl Default for Compiler +where + T: SolidityCompiler, +{ + fn default() -> Self { + Self::new() + } +} + /// Defines how the compiler should handle revert strings. #[derive( Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize, diff --git a/crates/compiler/src/revive_resolc.rs b/crates/compiler/src/revive_resolc.rs index 5e549a5..f8c5831 100644 --- a/crates/compiler/src/revive_resolc.rs +++ b/crates/compiler/src/revive_resolc.rs @@ -1,13 +1,8 @@ //! Implements the [SolidityCompiler] trait with `resolc` for //! compiling contracts to PolkaVM (PVM) bytecode. -use std::{ - path::PathBuf, - process::{Command, Stdio}, - sync::LazyLock, -}; +use std::{path::PathBuf, process::Stdio}; -use dashmap::DashMap; use revive_dt_common::types::VersionOrRequirement; use revive_dt_config::Arguments; use revive_solc_json_interface::{ @@ -17,6 +12,7 @@ use revive_solc_json_interface::{ }; use super::constants::SOLC_VERSION_SUPPORTING_VIA_YUL_IR; +use super::utils; use crate::{CompilerInput, CompilerOutput, ModeOptimizerSetting, ModePipeline, SolidityCompiler}; use alloy::json_abi::JsonAbi; @@ -31,6 +27,11 @@ use tokio::{io::AsyncWriteExt, process::Command as AsyncCommand}; /// A wrapper around the `resolc` binary, emitting PVM-compatible bytecode. #[derive(Debug)] pub struct Resolc { + // Where to cache artifacts. + cache_directory: PathBuf, + // We'll use this version when no explicit version + // requirement is given in the test mode. + solc_version: Version, /// Path to the `resolc` executable resolc_path: PathBuf, } @@ -42,8 +43,10 @@ impl SolidityCompiler for Resolc { async fn build( &self, CompilerInput { + wasm, pipeline, optimization, + solc_version, evm_version, allow_paths, base_path, @@ -61,6 +64,19 @@ impl SolidityCompiler for Resolc { ); } + let solc_version_req = solc_version + .unwrap_or_else(|| VersionOrRequirement::version_to_requirement(&self.solc_version)); + let solc_path = + revive_dt_solc_binaries::download_solc(&self.cache_directory, solc_version_req, wasm) + .await?; + let solc_version = utils::solc_version(&solc_path).await?; + + if solc_version < SOLC_VERSION_SUPPORTING_VIA_YUL_IR { + anyhow::bail!( + "We are trying to run the test with solc version {solc_version}, but require {SOLC_VERSION_SUPPORTING_VIA_YUL_IR} or greater" + ); + } + let input = SolcStandardJsonInput { language: SolcStandardJsonInputLanguage::Solidity, sources: sources @@ -106,6 +122,8 @@ impl SolidityCompiler for Resolc { .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) + .arg("--solc") + .arg(&solc_path) .arg("--standard-json"); if let Some(ref base_path) = base_path { @@ -206,86 +224,17 @@ impl SolidityCompiler for Resolc { Ok(compiler_output) } - fn new(resolc_path: PathBuf) -> Self { - Resolc { resolc_path } - } - - async fn get_compiler_executable( - config: &Arguments, - _version: impl Into, - ) -> anyhow::Result { - if !config.resolc.as_os_str().is_empty() { - return Ok(config.resolc.clone()); - } - - Ok(PathBuf::from("resolc")) - } - - async fn version(&self) -> anyhow::Result { - /// This is a cache of the path of the compiler to the version number of the compiler. We - /// choose to cache the version in this way rather than through a field on the struct since - /// compiler objects are being created all the time from the path and the compiler object is - /// not reused over time. - static VERSION_CACHE: LazyLock> = LazyLock::new(Default::default); - - match VERSION_CACHE.entry(self.resolc_path.clone()) { - dashmap::Entry::Occupied(occupied_entry) => Ok(occupied_entry.get().clone()), - dashmap::Entry::Vacant(vacant_entry) => { - 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")?; - - let version = Version::parse(version_string)?; - - vacant_entry.insert(version.clone()); - - Ok(version) - } + fn new(config: &Arguments) -> Self { + Resolc { + cache_directory: config.directory().to_path_buf(), + solc_version: config.solc.clone(), + resolc_path: config.resolc.clone(), } } - fn supports_mode( - compiler_version: &Version, - _optimize_setting: ModeOptimizerSetting, - pipeline: ModePipeline, - ) -> bool { - // We only support the Y (IE compile via Yul IR) mode here, which also means that we can - // only use solc version 0.8.13 and above. We must always compile via Yul IR as resolc - // needs this to translate to LLVM IR and then RISCV. + fn supports_mode(_optimize_setting: ModeOptimizerSetting, pipeline: ModePipeline) -> bool { + // We only support the Y (IE compile via Yul IR) mode here. We must always compile + // via Yul IR as resolc needs this to translate to LLVM IR and then RISCV. pipeline == ModePipeline::ViaYulIR - && compiler_version >= &SOLC_VERSION_SUPPORTING_VIA_YUL_IR - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[tokio::test] - async fn compiler_version_can_be_obtained() { - // Arrange - let args = Arguments::default(); - let path = Resolc::get_compiler_executable(&args, Version::new(0, 7, 6)) - .await - .unwrap(); - let compiler = Resolc::new(path); - - // Act - let version = compiler.version().await; - - // Assert - let _ = version.expect("Failed to get version"); } } diff --git a/crates/compiler/src/solc.rs b/crates/compiler/src/solc.rs index a7d8501..47df20a 100644 --- a/crates/compiler/src/solc.rs +++ b/crates/compiler/src/solc.rs @@ -1,18 +1,13 @@ //! Implements the [SolidityCompiler] trait with solc for //! compiling contracts to EVM bytecode. -use std::{ - path::PathBuf, - process::{Command, Stdio}, - sync::LazyLock, -}; +use std::{path::PathBuf, process::Stdio}; -use dashmap::DashMap; use revive_dt_common::types::VersionOrRequirement; use revive_dt_config::Arguments; -use revive_dt_solc_binaries::download_solc; use super::constants::SOLC_VERSION_SUPPORTING_VIA_YUL_IR; +use super::utils; use crate::{CompilerInput, CompilerOutput, ModeOptimizerSetting, ModePipeline, SolidityCompiler}; use anyhow::Context; @@ -28,7 +23,11 @@ use tokio::{io::AsyncWriteExt, process::Command as AsyncCommand}; #[derive(Debug)] pub struct Solc { - solc_path: PathBuf, + // Where to cache artifacts. + cache_directory: PathBuf, + // We'll use this version when no explicit version requirement + // is given in the test mode. + solc_version: Version, } impl SolidityCompiler for Solc { @@ -38,8 +37,10 @@ impl SolidityCompiler for Solc { async fn build( &self, CompilerInput { + wasm, pipeline, optimization, + solc_version, evm_version, allow_paths, base_path, @@ -49,7 +50,13 @@ impl SolidityCompiler for Solc { }: CompilerInput, _: Self::Options, ) -> anyhow::Result { - let compiler_supports_via_ir = self.version().await? >= SOLC_VERSION_SUPPORTING_VIA_YUL_IR; + let solc_version = solc_version + .unwrap_or_else(|| VersionOrRequirement::version_to_requirement(&self.solc_version)); + let solc_path = + revive_dt_solc_binaries::download_solc(&self.cache_directory, solc_version, wasm) + .await?; + let compiler_supports_via_ir = + utils::solc_version(&solc_path).await? >= SOLC_VERSION_SUPPORTING_VIA_YUL_IR; // Be careful to entirely omit the viaIR field if the compiler does not support it, // as it will error if you provide fields it does not know about. Because @@ -115,7 +122,7 @@ impl SolidityCompiler for Solc { }, }; - let mut command = AsyncCommand::new(&self.solc_path); + let mut command = AsyncCommand::new(&solc_path); command .stdin(Stdio::piped()) .stdout(Stdio::piped()) @@ -199,110 +206,16 @@ impl SolidityCompiler for Solc { Ok(compiler_output) } - fn new(solc_path: PathBuf) -> Self { - Self { solc_path } - } - - async fn get_compiler_executable( - config: &Arguments, - version: impl Into, - ) -> anyhow::Result { - let path = download_solc(config.directory(), version, config.wasm).await?; - Ok(path) - } - - async fn version(&self) -> anyhow::Result { - /// This is a cache of the path of the compiler to the version number of the compiler. We - /// choose to cache the version in this way rather than through a field on the struct since - /// compiler objects are being created all the time from the path and the compiler object is - /// not reused over time. - static VERSION_CACHE: LazyLock> = LazyLock::new(Default::default); - - match VERSION_CACHE.entry(self.solc_path.clone()) { - dashmap::Entry::Occupied(occupied_entry) => Ok(occupied_entry.get().clone()), - dashmap::Entry::Vacant(vacant_entry) => { - // 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")?; - - let version = Version::parse(version_string)?; - - vacant_entry.insert(version.clone()); - - Ok(version) - } + fn new(config: &Arguments) -> Self { + Self { + cache_directory: config.directory().to_path_buf(), + solc_version: config.solc.clone(), } } - fn supports_mode( - compiler_version: &Version, - _optimize_setting: ModeOptimizerSetting, - pipeline: ModePipeline, - ) -> bool { + fn supports_mode(_optimize_setting: ModeOptimizerSetting, pipeline: ModePipeline) -> bool { // solc 0.8.13 and above supports --via-ir, and less than that does not. Thus, we support mode E // (ie no Yul IR) in either case, but only support Y (via Yul IR) if the compiler is new enough. - pipeline == ModePipeline::ViaEVMAssembly - || (pipeline == ModePipeline::ViaYulIR - && compiler_version >= &SOLC_VERSION_SUPPORTING_VIA_YUL_IR) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[tokio::test] - async fn compiler_version_can_be_obtained() { - // Arrange - let args = Arguments::default(); - let path = Solc::get_compiler_executable(&args, Version::new(0, 7, 6)) - .await - .unwrap(); - let compiler = Solc::new(path); - - // Act - let version = compiler.version().await; - - // Assert - assert_eq!( - version.expect("Failed to get version"), - Version::new(0, 7, 6) - ) - } - - #[tokio::test] - async fn compiler_version_can_be_obtained1() { - // Arrange - let args = Arguments::default(); - let path = Solc::get_compiler_executable(&args, Version::new(0, 4, 21)) - .await - .unwrap(); - let compiler = Solc::new(path); - - // Act - let version = compiler.version().await; - - // Assert - assert_eq!( - version.expect("Failed to get version"), - Version::new(0, 4, 21) - ) + pipeline == ModePipeline::ViaEVMAssembly || pipeline == ModePipeline::ViaYulIR } } diff --git a/crates/compiler/src/utils.rs b/crates/compiler/src/utils.rs new file mode 100644 index 0000000..c348ed9 --- /dev/null +++ b/crates/compiler/src/utils.rs @@ -0,0 +1,50 @@ +use std::{ + path::{Path, PathBuf}, + process::{Command, Stdio}, + sync::LazyLock, +}; + +use anyhow::Context; +use dashmap::DashMap; +use semver::Version; + +/// Fetch the solc version given a path to the executable +pub async fn solc_version(solc_path: &Path) -> anyhow::Result { + /// This is a cache of the path of the compiler to the version number of the compiler. We + /// choose to cache the version in this way rather than through a field on the struct since + /// compiler objects are being created all the time from the path and the compiler object is + /// not reused over time. + static VERSION_CACHE: LazyLock> = LazyLock::new(Default::default); + + match VERSION_CACHE.entry(solc_path.to_path_buf()) { + dashmap::Entry::Occupied(occupied_entry) => Ok(occupied_entry.get().clone()), + dashmap::Entry::Vacant(vacant_entry) => { + // 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(solc_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")?; + + let version = Version::parse(version_string)?; + + vacant_entry.insert(version.clone()); + + Ok(version) + } + } +} diff --git a/crates/compiler/tests/lib.rs b/crates/compiler/tests/lib.rs index 80858f2..22754d3 100644 --- a/crates/compiler/tests/lib.rs +++ b/crates/compiler/tests/lib.rs @@ -1,16 +1,12 @@ use std::path::PathBuf; -use revive_dt_compiler::{Compiler, SolidityCompiler, revive_resolc::Resolc, solc::Solc}; +use revive_dt_compiler::{Compiler, revive_resolc::Resolc, solc::Solc}; use revive_dt_config::Arguments; -use semver::Version; #[tokio::test] async fn contracts_can_be_compiled_with_solc() { // Arrange let args = Arguments::default(); - let compiler_path = Solc::get_compiler_executable(&args, Version::new(0, 8, 30)) - .await - .unwrap(); // Act let output = Compiler::::new() @@ -18,7 +14,7 @@ async fn contracts_can_be_compiled_with_solc() { .unwrap() .with_source("./tests/assets/array_one_element/main.sol") .unwrap() - .try_build(compiler_path) + .try_build(&args) .await; // Assert @@ -49,9 +45,6 @@ async fn contracts_can_be_compiled_with_solc() { async fn contracts_can_be_compiled_with_resolc() { // Arrange let args = Arguments::default(); - let compiler_path = Resolc::get_compiler_executable(&args, Version::new(0, 8, 30)) - .await - .unwrap(); // Act let output = Compiler::::new() @@ -59,7 +52,7 @@ async fn contracts_can_be_compiled_with_resolc() { .unwrap() .with_source("./tests/assets/array_one_element/main.sol") .unwrap() - .try_build(compiler_path) + .try_build(&args) .await; // Assert diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index a4b2221..369fc08 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -20,6 +20,7 @@ revive-dt-format = { workspace = true } revive-dt-node = { workspace = true } revive-dt-node-interaction = { workspace = true } revive-dt-report = { workspace = true } +revive-dt-solc-binaries = { workspace = true } alloy = { workspace = true } anyhow = { workspace = true } diff --git a/crates/core/src/cached_compiler.rs b/crates/core/src/cached_compiler.rs index b94b1f3..f30feff 100644 --- a/crates/core/src/cached_compiler.rs +++ b/crates/core/src/cached_compiler.rs @@ -9,9 +9,10 @@ use std::{ use futures::FutureExt; use revive_dt_common::iterators::FilesWithExtensionIterator; -use revive_dt_compiler::{Compiler, CompilerOutput, Mode, SolidityCompiler}; +use revive_dt_compiler::{Compiler, CompilerOutput, Mode}; use revive_dt_config::Arguments; use revive_dt_format::metadata::{ContractIdent, ContractInstance, Metadata}; +use revive_dt_solc_binaries::solc_version; use alloy::{hex::ToHexExt, json_abi::JsonAbi, primitives::Address}; use anyhow::{Error, Result}; @@ -57,14 +58,7 @@ impl CachedCompiler { Lazy::new(Default::default); let compiler_version_or_requirement = mode.compiler_version_to_use(config.solc.clone()); - let compiler_path = ::get_compiler_executable( - config, - compiler_version_or_requirement, - ) - .await?; - let compiler_version = ::new(compiler_path.clone()) - .version() - .await?; + let compiler_version = solc_version(compiler_version_or_requirement, config.wasm).await?; let cache_key = CacheKey { platform_key: P::config_id().to_string(), @@ -77,8 +71,8 @@ impl CachedCompiler { async move { compile_contracts::

( metadata.directory()?, - compiler_path, metadata.files_to_compile()?, + config, mode, deployed_libraries, ) @@ -138,8 +132,8 @@ impl CachedCompiler { async fn compile_contracts( metadata_directory: impl AsRef, - compiler_path: impl AsRef, mut files_to_compile: impl Iterator, + config: &Arguments, mode: &Mode, deployed_libraries: Option<&HashMap>, ) -> Result { @@ -149,6 +143,7 @@ async fn compile_contracts( .collect::>(); Compiler::::new() + .with_solc_version_req(mode.version.clone()) .with_allow_path(metadata_directory) // Handling the modes .with_optimization(mode.optimize_setting) @@ -172,7 +167,7 @@ async fn compile_contracts( compiler.with_library(path, ident.as_str(), *address) }) }) - .try_build(compiler_path) + .try_build(config) .await } diff --git a/crates/core/src/main.rs b/crates/core/src/main.rs index f74101d..c75dbad 100644 --- a/crates/core/src/main.rs +++ b/crates/core/src/main.rs @@ -14,8 +14,8 @@ use alloy::{ }; use anyhow::Context; use clap::Parser; +use futures::StreamExt; use futures::stream; -use futures::{Stream, StreamExt}; use indexmap::IndexMap; use revive_dt_node_interaction::EthereumNode; use temp_dir::TempDir; @@ -163,7 +163,7 @@ where { let (report_tx, report_rx) = mpsc::unbounded_channel::<(Test<'_>, CaseResult)>(); - let tests = prepare_tests::(args, metadata_files); + let tests = prepare_tests::(metadata_files); let driver_task = start_driver_task::(args, tests, span, report_tx).await?; let status_reporter_task = start_reporter_task(report_rx); @@ -172,17 +172,14 @@ where Ok(()) } -fn prepare_tests<'a, L, F>( - args: &Arguments, - metadata_files: &'a [MetadataFile], -) -> impl Stream> +fn prepare_tests<'a, L, F>(metadata_files: &'a [MetadataFile]) -> impl Iterator> where L: Platform, F: Platform, L::Blockchain: revive_dt_node::Node + Send + Sync + 'static, F::Blockchain: revive_dt_node::Node + Send + Sync + 'static, { - let filtered_tests = metadata_files + metadata_files .iter() .flat_map(|metadata_file| { metadata_file @@ -289,19 +286,12 @@ where } else { true } - }); - - stream::iter(filtered_tests) - // Filter based on the compiler compatibility - .filter_map(move |test| async move { - let leader_support = does_compiler_support_mode::(args, &test.mode) - .await - .ok() - .unwrap_or(false); - let follower_support = does_compiler_support_mode::(args, &test.mode) - .await - .ok() - .unwrap_or(false); + }) + .filter_map(move |test| { + let leader_support = + L::Compiler::supports_mode(test.mode.optimize_setting, test.mode.pipeline); + let follower_support = + F::Compiler::supports_mode(test.mode.optimize_setting, test.mode.pipeline); let is_allowed = leader_support && follower_support; if !is_allowed { @@ -317,25 +307,9 @@ where }) } -async fn does_compiler_support_mode( - args: &Arguments, - mode: &Mode, -) -> anyhow::Result { - let compiler_version_or_requirement = mode.compiler_version_to_use(args.solc.clone()); - let compiler_path = - P::Compiler::get_compiler_executable(args, compiler_version_or_requirement).await?; - let compiler_version = P::Compiler::new(compiler_path.clone()).version().await?; - - Ok(P::Compiler::supports_mode( - &compiler_version, - mode.optimize_setting, - mode.pipeline, - )) -} - async fn start_driver_task<'a, L, F>( args: &Arguments, - tests: impl Stream>, + tests: impl Iterator>, span: Span, report_tx: mpsc::UnboundedSender<(Test<'a>, CaseResult)>, ) -> anyhow::Result> @@ -356,7 +330,7 @@ where .await?, ); - Ok(tests.for_each_concurrent( + Ok(stream::iter(tests).for_each_concurrent( // We want to limit the concurrent tasks here because: // // 1. We don't want to overwhelm the nodes with too many requests, leading to responses timing out. diff --git a/crates/solc-binaries/src/lib.rs b/crates/solc-binaries/src/lib.rs index 05d0de5..a377751 100644 --- a/crates/solc-binaries/src/lib.rs +++ b/crates/solc-binaries/src/lib.rs @@ -7,6 +7,7 @@ use std::path::{Path, PathBuf}; use cache::get_or_download; use download::SolcDownloader; +use semver::Version; use revive_dt_common::types::VersionOrRequirement; @@ -14,6 +15,25 @@ pub mod cache; pub mod download; pub mod list; +/// Return a [`SolcDownloader`] which can be used to download a `solc` +/// binary or return the resolved version it will download. +async fn downloader( + version: impl Into, + wasm: bool, +) -> anyhow::Result { + if wasm { + SolcDownloader::wasm(version).await + } else if cfg!(target_os = "linux") { + SolcDownloader::linux(version).await + } else if cfg!(target_os = "macos") { + SolcDownloader::macosx(version).await + } else if cfg!(target_os = "windows") { + SolcDownloader::windows(version).await + } else { + unimplemented!() + } +} + /// Downloads the solc binary for Wasm is `wasm` is set, otherwise for /// the target platform. /// @@ -24,17 +44,16 @@ pub async fn download_solc( version: impl Into, wasm: bool, ) -> anyhow::Result { - let downloader = if wasm { - SolcDownloader::wasm(version).await - } else if cfg!(target_os = "linux") { - SolcDownloader::linux(version).await - } else if cfg!(target_os = "macos") { - SolcDownloader::macosx(version).await - } else if cfg!(target_os = "windows") { - SolcDownloader::windows(version).await - } else { - unimplemented!() - }?; - + let downloader = downloader(version, wasm).await?; get_or_download(cache_directory, &downloader).await } + +/// Return the version of solc that will be downloaded via [`download_solc`] +/// given the version requirements and wasm flag. +pub async fn solc_version( + version: impl Into, + wasm: bool, +) -> anyhow::Result { + let downloader = downloader(version, wasm).await?; + Ok(downloader.version.clone()) +}