mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-06-13 12:51:03 +00:00
Support multiple compiler versions (#92)
* Allow for downloader to use version requirements. We will soon add support for the compiler version requirement from the metadata files to be honored. The compiler version is specified in the solc modes section of the file and its specified as a `VersionReq` and not as a version. Therefore, we need to have the ability to honor this version requirement and find the best version that satisfies the requirement. * Request `VersionOrRequirement` in compiler interface * 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. * Make compiler IO completely generic. Before this commit, the types that were used for the compiler input and output were the resolc compiler types which was a leaky abstraction as we have traits to abstract the compilers away but we expose their internal types out to other crates. This commit did the following: 1. Made the compiler IO types fully generic so that all of the logic for constructing the map of compiled contracts is all done by the compiler implementation and not by the consuming code. 2. Changed the input types used for Solc to be the forge standard JSON types for Solc instead of resolc. * Fix machete * Add resolc to CI * Add resolc to CI * Add resolc to CI * Add resolc to CI
This commit is contained in:
+162
-30
@@ -6,10 +6,21 @@ 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_solc;
|
||||
use revive_solc_json_interface::SolcStandardJsonOutput;
|
||||
|
||||
use crate::{CompilerInput, CompilerOutput, SolidityCompiler};
|
||||
|
||||
use anyhow::Context;
|
||||
use foundry_compilers_artifacts::{
|
||||
output_selection::{
|
||||
BytecodeOutputSelection, ContractOutputSelection, EvmOutputSelection, OutputSelection,
|
||||
},
|
||||
solc::CompilerOutput as SolcOutput,
|
||||
solc::*,
|
||||
};
|
||||
use semver::Version;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Solc {
|
||||
@@ -22,8 +33,63 @@ impl SolidityCompiler for Solc {
|
||||
#[tracing::instrument(level = "debug", ret)]
|
||||
fn build(
|
||||
&self,
|
||||
input: CompilerInput<Self::Options>,
|
||||
) -> anyhow::Result<CompilerOutput<Self::Options>> {
|
||||
CompilerInput {
|
||||
enable_optimization,
|
||||
via_ir,
|
||||
evm_version,
|
||||
allow_paths,
|
||||
base_path,
|
||||
sources,
|
||||
libraries,
|
||||
}: CompilerInput,
|
||||
_: Self::Options,
|
||||
) -> anyhow::Result<CompilerOutput> {
|
||||
let input = SolcInput {
|
||||
language: SolcLanguage::Solidity,
|
||||
sources: Sources(
|
||||
sources
|
||||
.into_iter()
|
||||
.map(|(source_path, source_code)| (source_path, Source::new(source_code)))
|
||||
.collect(),
|
||||
),
|
||||
settings: Settings {
|
||||
optimizer: Optimizer {
|
||||
enabled: enable_optimization,
|
||||
details: Some(Default::default()),
|
||||
..Default::default()
|
||||
},
|
||||
output_selection: OutputSelection::common_output_selection(
|
||||
[
|
||||
ContractOutputSelection::Abi,
|
||||
ContractOutputSelection::Evm(EvmOutputSelection::ByteCode(
|
||||
BytecodeOutputSelection::Object,
|
||||
)),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|item| item.to_string()),
|
||||
),
|
||||
evm_version: evm_version.map(|version| version.to_string().parse().unwrap()),
|
||||
via_ir,
|
||||
libraries: Libraries {
|
||||
libs: libraries
|
||||
.into_iter()
|
||||
.map(|(file_path, libraries)| {
|
||||
(
|
||||
file_path,
|
||||
libraries
|
||||
.into_iter()
|
||||
.map(|(library_name, library_address)| {
|
||||
(library_name, library_address.to_string())
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
|
||||
let mut command = Command::new(&self.solc_path);
|
||||
command
|
||||
.stdin(Stdio::piped())
|
||||
@@ -31,13 +97,12 @@ impl SolidityCompiler for Solc {
|
||||
.stderr(Stdio::piped())
|
||||
.arg("--standard-json");
|
||||
|
||||
if let Some(ref base_path) = input.base_path {
|
||||
if let Some(ref base_path) = base_path {
|
||||
command.arg("--base-path").arg(base_path);
|
||||
}
|
||||
if !input.allow_paths.is_empty() {
|
||||
if !allow_paths.is_empty() {
|
||||
command.arg("--allow-paths").arg(
|
||||
input
|
||||
.allow_paths
|
||||
allow_paths
|
||||
.iter()
|
||||
.map(|path| path.display().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
@@ -47,31 +112,32 @@ impl SolidityCompiler for Solc {
|
||||
let mut child = command.spawn()?;
|
||||
|
||||
let stdin = child.stdin.as_mut().expect("should be piped");
|
||||
serde_json::to_writer(stdin, &input.input)?;
|
||||
serde_json::to_writer(stdin, &input)?;
|
||||
let output = child.wait_with_output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
let json_in = serde_json::to_string_pretty(&input)?;
|
||||
let message = String::from_utf8_lossy(&output.stderr);
|
||||
tracing::error!("solc failed exit={} stderr={}", output.status, &message);
|
||||
return Ok(CompilerOutput {
|
||||
input,
|
||||
output: Default::default(),
|
||||
error: Some(message.into()),
|
||||
});
|
||||
tracing::error!(
|
||||
status = %output.status,
|
||||
message = %message,
|
||||
json_input = json_in,
|
||||
"Compilation using solc failed"
|
||||
);
|
||||
anyhow::bail!("Compilation failed with an error: {message}");
|
||||
}
|
||||
|
||||
let parsed =
|
||||
serde_json::from_slice::<SolcStandardJsonOutput>(&output.stdout).map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to parse resolc JSON output: {e}\nstderr: {}",
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
)
|
||||
})?;
|
||||
let parsed = serde_json::from_slice::<SolcOutput>(&output.stdout).map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to parse resolc JSON output: {e}\nstderr: {}",
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
)
|
||||
})?;
|
||||
|
||||
// Detecting if the compiler output contained errors and reporting them through logs and
|
||||
// errors instead of returning the compiler output that might contain errors.
|
||||
for error in parsed.errors.iter().flatten() {
|
||||
if error.severity == "error" {
|
||||
for error in parsed.errors.iter() {
|
||||
if error.severity == Severity::Error {
|
||||
tracing::error!(?error, ?input, "Encountered an error in the compilation");
|
||||
anyhow::bail!("Encountered an error in the compilation: {error}")
|
||||
}
|
||||
@@ -82,11 +148,29 @@ impl SolidityCompiler for Solc {
|
||||
"Compiled successfully"
|
||||
);
|
||||
|
||||
Ok(CompilerOutput {
|
||||
input,
|
||||
output: parsed,
|
||||
error: None,
|
||||
})
|
||||
let mut compiler_output = CompilerOutput::default();
|
||||
for (contract_path, contracts) in parsed.contracts {
|
||||
let map = compiler_output
|
||||
.contracts
|
||||
.entry(contract_path.canonicalize()?)
|
||||
.or_default();
|
||||
for (contract_name, contract_info) in contracts.into_iter() {
|
||||
let source_code = contract_info
|
||||
.evm
|
||||
.and_then(|evm| evm.bytecode)
|
||||
.map(|bytecode| match bytecode.object {
|
||||
BytecodeObject::Bytecode(bytecode) => bytecode.to_string(),
|
||||
BytecodeObject::Unlinked(unlinked) => unlinked,
|
||||
})
|
||||
.context("Unexpected - contract compiled with solc has no source code")?;
|
||||
let abi = contract_info
|
||||
.abi
|
||||
.context("Unexpected - contract compiled with solc as no ABI")?;
|
||||
map.insert(contract_name, (source_code, abi));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(compiler_output)
|
||||
}
|
||||
|
||||
fn new(solc_path: PathBuf) -> Self {
|
||||
@@ -95,9 +179,57 @@ impl SolidityCompiler for Solc {
|
||||
|
||||
fn get_compiler_executable(
|
||||
config: &Arguments,
|
||||
version: semver::Version,
|
||||
version: impl Into<VersionOrRequirement>,
|
||||
) -> anyhow::Result<PathBuf> {
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user