mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-05-30 14:51:01 +00:00
Use the solc version required in tests rather than one on PATH
This commit is contained in:
Generated
+1
@@ -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",
|
||||
|
||||
@@ -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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
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<Version> for VersionOrRequirement {
|
||||
fn from(value: Version) -> Self {
|
||||
Self::Version(value)
|
||||
@@ -18,24 +54,45 @@ impl From<VersionReq> for VersionOrRequirement {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VersionOrRequirement> 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<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)
|
||||
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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+31
-27
@@ -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<Output = anyhow::Result<CompilerOutput>>;
|
||||
|
||||
fn new(solc_executable: PathBuf) -> Self;
|
||||
|
||||
fn get_compiler_executable(
|
||||
config: &Arguments,
|
||||
version: impl Into<VersionOrRequirement>,
|
||||
) -> impl Future<Output = anyhow::Result<PathBuf>>;
|
||||
|
||||
fn version(&self) -> impl Future<Output = anyhow::Result<Version>>;
|
||||
/// 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<ModePipeline>,
|
||||
pub optimization: Option<ModeOptimizerSetting>,
|
||||
pub solc_version: Option<VersionReq>,
|
||||
pub evm_version: Option<EVMVersion>,
|
||||
pub allow_paths: Vec<PathBuf>,
|
||||
pub base_path: Option<PathBuf>,
|
||||
@@ -84,12 +76,6 @@ pub struct Compiler<T: SolidityCompiler> {
|
||||
additional_options: T::Options,
|
||||
}
|
||||
|
||||
impl Default for Compiler<solc::Solc> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Compiler<T>
|
||||
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<Option<VersionReq>>) -> Self {
|
||||
self.input.solc_version = value.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_optimization(mut self, value: impl Into<Option<ModeOptimizerSetting>>) -> Self {
|
||||
self.input.optimization = value.into();
|
||||
self
|
||||
@@ -177,11 +175,8 @@ where
|
||||
callback(self)
|
||||
}
|
||||
|
||||
pub async fn try_build(
|
||||
self,
|
||||
compiler_path: impl AsRef<Path>,
|
||||
) -> anyhow::Result<CompilerOutput> {
|
||||
T::new(compiler_path.as_ref().to_path_buf())
|
||||
pub async fn try_build(self, config: &Arguments) -> anyhow::Result<CompilerOutput> {
|
||||
T::new(config)
|
||||
.build(self.input, self.additional_options)
|
||||
.await
|
||||
}
|
||||
@@ -191,6 +186,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Compiler<T>
|
||||
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,
|
||||
|
||||
@@ -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<VersionOrRequirement>,
|
||||
) -> anyhow::Result<PathBuf> {
|
||||
if !config.resolc.as_os_str().is_empty() {
|
||||
return Ok(config.resolc.clone());
|
||||
}
|
||||
|
||||
Ok(PathBuf::from("resolc"))
|
||||
}
|
||||
|
||||
async fn version(&self) -> anyhow::Result<semver::Version> {
|
||||
/// 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<DashMap<PathBuf, Version>> = 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");
|
||||
}
|
||||
}
|
||||
|
||||
+23
-110
@@ -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<CompilerOutput> {
|
||||
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<VersionOrRequirement>,
|
||||
) -> anyhow::Result<PathBuf> {
|
||||
let path = download_solc(config.directory(), version, config.wasm).await?;
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
async fn version(&self) -> anyhow::Result<semver::Version> {
|
||||
/// 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<DashMap<PathBuf, Version>> = 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<semver::Version> {
|
||||
/// 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<DashMap<PathBuf, Version>> = 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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::<Solc>::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::<Resolc>::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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 = <P::Compiler as SolidityCompiler>::get_compiler_executable(
|
||||
config,
|
||||
compiler_version_or_requirement,
|
||||
)
|
||||
.await?;
|
||||
let compiler_version = <P::Compiler as SolidityCompiler>::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::<P>(
|
||||
metadata.directory()?,
|
||||
compiler_path,
|
||||
metadata.files_to_compile()?,
|
||||
config,
|
||||
mode,
|
||||
deployed_libraries,
|
||||
)
|
||||
@@ -138,8 +132,8 @@ impl CachedCompiler {
|
||||
|
||||
async fn compile_contracts<P: Platform>(
|
||||
metadata_directory: impl AsRef<Path>,
|
||||
compiler_path: impl AsRef<Path>,
|
||||
mut files_to_compile: impl Iterator<Item = PathBuf>,
|
||||
config: &Arguments,
|
||||
mode: &Mode,
|
||||
deployed_libraries: Option<&HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>>,
|
||||
) -> Result<CompilerOutput> {
|
||||
@@ -149,6 +143,7 @@ async fn compile_contracts<P: Platform>(
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Compiler::<P::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<P: Platform>(
|
||||
compiler.with_library(path, ident.as_str(), *address)
|
||||
})
|
||||
})
|
||||
.try_build(compiler_path)
|
||||
.try_build(config)
|
||||
.await
|
||||
}
|
||||
|
||||
|
||||
+12
-38
@@ -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::<L, F>(args, metadata_files);
|
||||
let tests = prepare_tests::<L, F>(metadata_files);
|
||||
let driver_task = start_driver_task::<L, F>(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<Item = Test<'a>>
|
||||
fn prepare_tests<'a, L, F>(metadata_files: &'a [MetadataFile]) -> impl Iterator<Item = Test<'a>>
|
||||
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::<L>(args, &test.mode)
|
||||
.await
|
||||
.ok()
|
||||
.unwrap_or(false);
|
||||
let follower_support = does_compiler_support_mode::<F>(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<P: Platform>(
|
||||
args: &Arguments,
|
||||
mode: &Mode,
|
||||
) -> anyhow::Result<bool> {
|
||||
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<Item = Test<'a>>,
|
||||
tests: impl Iterator<Item = Test<'a>>,
|
||||
span: Span,
|
||||
report_tx: mpsc::UnboundedSender<(Test<'a>, CaseResult)>,
|
||||
) -> anyhow::Result<impl Future<Output = ()>>
|
||||
@@ -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.
|
||||
|
||||
@@ -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<VersionOrRequirement>,
|
||||
wasm: bool,
|
||||
) -> anyhow::Result<SolcDownloader> {
|
||||
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<VersionOrRequirement>,
|
||||
wasm: bool,
|
||||
) -> anyhow::Result<PathBuf> {
|
||||
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<VersionOrRequirement>,
|
||||
wasm: bool,
|
||||
) -> anyhow::Result<Version> {
|
||||
let downloader = downloader(version, wasm).await?;
|
||||
Ok(downloader.version.clone())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user