mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-06-14 06:21:09 +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:
@@ -6,9 +6,19 @@ use std::{
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use crate::{CompilerInput, CompilerOutput, SolidityCompiler};
|
||||
use alloy::json_abi::JsonAbi;
|
||||
use revive_dt_common::types::VersionOrRequirement;
|
||||
use revive_dt_config::Arguments;
|
||||
use revive_solc_json_interface::SolcStandardJsonOutput;
|
||||
use revive_solc_json_interface::{
|
||||
SolcStandardJsonInput, SolcStandardJsonInputLanguage, SolcStandardJsonInputSettings,
|
||||
SolcStandardJsonInputSettingsOptimizer, SolcStandardJsonInputSettingsSelection,
|
||||
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
|
||||
@@ -27,8 +37,56 @@ impl SolidityCompiler for Resolc {
|
||||
#[tracing::instrument(level = "debug", ret)]
|
||||
fn build(
|
||||
&self,
|
||||
input: CompilerInput<Self::Options>,
|
||||
) -> anyhow::Result<CompilerOutput<Self::Options>> {
|
||||
CompilerInput {
|
||||
enable_optimization,
|
||||
// Ignored and not honored since this is required for the resolc compilation.
|
||||
via_ir: _via_ir,
|
||||
evm_version,
|
||||
allow_paths,
|
||||
base_path,
|
||||
sources,
|
||||
libraries,
|
||||
}: CompilerInput,
|
||||
additional_options: Self::Options,
|
||||
) -> anyhow::Result<CompilerOutput> {
|
||||
let input = SolcStandardJsonInput {
|
||||
language: SolcStandardJsonInputLanguage::Solidity,
|
||||
sources: sources
|
||||
.into_iter()
|
||||
.map(|(path, source)| (path.display().to_string(), source.into()))
|
||||
.collect(),
|
||||
settings: SolcStandardJsonInputSettings {
|
||||
evm_version,
|
||||
libraries: Some(
|
||||
libraries
|
||||
.into_iter()
|
||||
.map(|(source_code, libraries_map)| {
|
||||
(
|
||||
source_code.display().to_string(),
|
||||
libraries_map
|
||||
.into_iter()
|
||||
.map(|(library_ident, library_address)| {
|
||||
(library_ident, library_address.to_string())
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
remappings: None,
|
||||
output_selection: Some(SolcStandardJsonInputSettingsSelection::new_required()),
|
||||
via_ir: Some(true),
|
||||
optimizer: SolcStandardJsonInputSettingsOptimizer::new(
|
||||
enable_optimization.unwrap_or(false),
|
||||
None,
|
||||
&Version::new(0, 0, 0),
|
||||
false,
|
||||
),
|
||||
metadata: None,
|
||||
polkavm: None,
|
||||
},
|
||||
};
|
||||
|
||||
let mut command = Command::new(&self.resolc_path);
|
||||
command
|
||||
.stdin(Stdio::piped())
|
||||
@@ -36,13 +94,12 @@ impl SolidityCompiler for Resolc {
|
||||
.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<_>>()
|
||||
@@ -52,93 +109,86 @@ impl SolidityCompiler for Resolc {
|
||||
let mut child = command.spawn()?;
|
||||
|
||||
let stdin_pipe = child.stdin.as_mut().expect("stdin must be piped");
|
||||
serde_json::to_writer(stdin_pipe, &input.input)?;
|
||||
|
||||
let json_in = serde_json::to_string_pretty(&input.input)?;
|
||||
serde_json::to_writer(stdin_pipe, &input)?;
|
||||
|
||||
let output = child.wait_with_output()?;
|
||||
let stdout = output.stdout;
|
||||
let stderr = output.stderr;
|
||||
|
||||
if !output.status.success() {
|
||||
let json_in = serde_json::to_string_pretty(&input)?;
|
||||
let message = String::from_utf8_lossy(&stderr);
|
||||
tracing::error!(
|
||||
"resolc failed exit={} stderr={} JSON-in={} ",
|
||||
output.status,
|
||||
&message,
|
||||
json_in,
|
||||
status = %output.status,
|
||||
message = %message,
|
||||
json_input = json_in,
|
||||
"Compilation using resolc failed"
|
||||
);
|
||||
return Ok(CompilerOutput {
|
||||
input,
|
||||
output: Default::default(),
|
||||
error: Some(message.into()),
|
||||
});
|
||||
anyhow::bail!("Compilation failed with an error: {message}");
|
||||
}
|
||||
|
||||
let mut parsed =
|
||||
serde_json::from_slice::<SolcStandardJsonOutput>(&stdout).map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to parse resolc JSON output: {e}\nstderr: {}",
|
||||
String::from_utf8_lossy(&stderr)
|
||||
)
|
||||
})?;
|
||||
|
||||
// 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" {
|
||||
tracing::error!(?error, ?input, "Encountered an error in the compilation");
|
||||
anyhow::bail!("Encountered an error in the compilation: {error}")
|
||||
}
|
||||
}
|
||||
|
||||
// We need to do some post processing on the output to make it in the same format that solc
|
||||
// outputs. More specifically, for each contract, the `.metadata` field should be replaced
|
||||
// with the `.metadata.solc_metadata` field which contains the ABI and other information
|
||||
// about the compiled contracts. We do this because we do not want any downstream logic to
|
||||
// need to differentiate between which compiler is being used when extracting the ABI of the
|
||||
// contracts.
|
||||
if let Some(ref mut contracts) = parsed.contracts {
|
||||
for (contract_path, contracts_map) in contracts.iter_mut() {
|
||||
for (contract_name, contract_info) in contracts_map.iter_mut() {
|
||||
let Some(metadata) = contract_info.metadata.take() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Get the `solc_metadata` in the metadata of the contract.
|
||||
let Some(solc_metadata) = metadata
|
||||
.get("solc_metadata")
|
||||
.and_then(|metadata| metadata.as_str())
|
||||
else {
|
||||
tracing::error!(
|
||||
contract_path,
|
||||
contract_name,
|
||||
metadata = serde_json::to_string(&metadata).unwrap(),
|
||||
"Encountered a contract compiled with resolc that has no solc_metadata"
|
||||
);
|
||||
anyhow::bail!(
|
||||
"Contract {} compiled with resolc that has no solc_metadata",
|
||||
contract_name
|
||||
);
|
||||
};
|
||||
|
||||
// Replace the original metadata with the new solc_metadata.
|
||||
contract_info.metadata =
|
||||
Some(serde_json::Value::String(solc_metadata.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
let parsed = serde_json::from_slice::<SolcStandardJsonOutput>(&stdout).map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to parse resolc JSON output: {e}\nstderr: {}",
|
||||
String::from_utf8_lossy(&stderr)
|
||||
)
|
||||
})?;
|
||||
|
||||
tracing::debug!(
|
||||
output = %serde_json::to_string(&parsed).unwrap(),
|
||||
"Compiled successfully"
|
||||
);
|
||||
|
||||
Ok(CompilerOutput {
|
||||
input,
|
||||
output: parsed,
|
||||
error: None,
|
||||
})
|
||||
// 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" {
|
||||
tracing::error!(
|
||||
?error,
|
||||
?input,
|
||||
output = %serde_json::to_string(&parsed).unwrap(),
|
||||
"Encountered an error in the compilation"
|
||||
);
|
||||
anyhow::bail!("Encountered an error in the compilation: {error}")
|
||||
}
|
||||
}
|
||||
|
||||
let Some(contracts) = parsed.contracts else {
|
||||
anyhow::bail!("Unexpected error - resolc output doesn't have a contracts section");
|
||||
};
|
||||
|
||||
let mut compiler_output = CompilerOutput::default();
|
||||
for (source_path, contracts) in contracts.into_iter() {
|
||||
let source_path = PathBuf::from(source_path).canonicalize()?;
|
||||
|
||||
let map = compiler_output.contracts.entry(source_path).or_default();
|
||||
for (contract_name, contract_information) in contracts.into_iter() {
|
||||
let bytecode = contract_information
|
||||
.evm
|
||||
.and_then(|evm| evm.bytecode.clone())
|
||||
.context("Unexpected - Contract compiled with resolc has no bytecode")?;
|
||||
let abi = contract_information
|
||||
.metadata
|
||||
.as_ref()
|
||||
.and_then(|metadata| metadata.as_object())
|
||||
.and_then(|metadata| metadata.get("solc_metadata"))
|
||||
.and_then(|solc_metadata| solc_metadata.as_str())
|
||||
.and_then(|metadata| serde_json::from_str::<serde_json::Value>(metadata).ok())
|
||||
.and_then(|metadata| {
|
||||
metadata.get("output").and_then(|output| {
|
||||
output
|
||||
.get("abi")
|
||||
.and_then(|abi| serde_json::from_value::<JsonAbi>(abi.clone()).ok())
|
||||
})
|
||||
})
|
||||
.context(
|
||||
"Unexpected - Failed to get the ABI for a contract compiled with resolc",
|
||||
)?;
|
||||
map.insert(contract_name, (bytecode.object, abi));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(compiler_output)
|
||||
}
|
||||
|
||||
fn new(resolc_path: PathBuf) -> Self {
|
||||
@@ -147,7 +197,7 @@ impl SolidityCompiler for Resolc {
|
||||
|
||||
fn get_compiler_executable(
|
||||
config: &Arguments,
|
||||
_version: semver::Version,
|
||||
_version: impl Into<VersionOrRequirement>,
|
||||
) -> anyhow::Result<PathBuf> {
|
||||
if !config.resolc.as_os_str().is_empty() {
|
||||
return Ok(config.resolc.clone());
|
||||
@@ -155,4 +205,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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user