mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-06-13 10:31:03 +00:00
Use the solc version required in tests rather than one on PATH
This commit is contained in:
+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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user