mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-06-12 22:51:10 +00:00
use polkadot-sdk rustfmt
This commit is contained in:
+111
-112
@@ -4,14 +4,13 @@
|
||||
//! - Polkadot revive Wasm compiler
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
hash::Hash,
|
||||
path::{Path, PathBuf},
|
||||
pin::Pin,
|
||||
collections::HashMap,
|
||||
hash::Hash,
|
||||
path::{Path, PathBuf},
|
||||
pin::Pin,
|
||||
};
|
||||
|
||||
use alloy::json_abi::JsonAbi;
|
||||
use alloy::primitives::Address;
|
||||
use alloy::{json_abi::JsonAbi, primitives::Address};
|
||||
use anyhow::{Context as _, Result};
|
||||
use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -28,149 +27,149 @@ pub mod solc;
|
||||
|
||||
/// A common interface for all supported Solidity compilers.
|
||||
pub trait SolidityCompiler {
|
||||
/// Returns the version of the compiler.
|
||||
fn version(&self) -> &Version;
|
||||
/// Returns the version of the compiler.
|
||||
fn version(&self) -> &Version;
|
||||
|
||||
/// Returns the path of the compiler executable.
|
||||
fn path(&self) -> &Path;
|
||||
/// Returns the path of the compiler executable.
|
||||
fn path(&self) -> &Path;
|
||||
|
||||
/// The low-level compiler interface.
|
||||
fn build(
|
||||
&self,
|
||||
input: CompilerInput,
|
||||
) -> Pin<Box<dyn Future<Output = Result<CompilerOutput>> + '_>>;
|
||||
/// The low-level compiler interface.
|
||||
fn build(
|
||||
&self,
|
||||
input: CompilerInput,
|
||||
) -> Pin<Box<dyn Future<Output = Result<CompilerOutput>> + '_>>;
|
||||
|
||||
/// Does the compiler support the provided mode and version settings.
|
||||
fn supports_mode(
|
||||
&self,
|
||||
optimizer_setting: ModeOptimizerSetting,
|
||||
pipeline: ModePipeline,
|
||||
) -> bool;
|
||||
/// Does the compiler support the provided mode and version settings.
|
||||
fn supports_mode(
|
||||
&self,
|
||||
optimizer_setting: ModeOptimizerSetting,
|
||||
pipeline: ModePipeline,
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
/// The generic compilation input configuration.
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct CompilerInput {
|
||||
pub pipeline: Option<ModePipeline>,
|
||||
pub optimization: Option<ModeOptimizerSetting>,
|
||||
pub evm_version: Option<EVMVersion>,
|
||||
pub allow_paths: Vec<PathBuf>,
|
||||
pub base_path: Option<PathBuf>,
|
||||
pub sources: HashMap<PathBuf, String>,
|
||||
pub libraries: HashMap<PathBuf, HashMap<String, Address>>,
|
||||
pub revert_string_handling: Option<RevertString>,
|
||||
pub pipeline: Option<ModePipeline>,
|
||||
pub optimization: Option<ModeOptimizerSetting>,
|
||||
pub evm_version: Option<EVMVersion>,
|
||||
pub allow_paths: Vec<PathBuf>,
|
||||
pub base_path: Option<PathBuf>,
|
||||
pub sources: HashMap<PathBuf, String>,
|
||||
pub libraries: HashMap<PathBuf, HashMap<String, Address>>,
|
||||
pub revert_string_handling: Option<RevertString>,
|
||||
}
|
||||
|
||||
/// The generic compilation output configuration.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct CompilerOutput {
|
||||
/// The compiled contracts. The bytecode of the contract is kept as a string in case linking is
|
||||
/// required and the compiled source has placeholders.
|
||||
pub contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
||||
/// The compiled contracts. The bytecode of the contract is kept as a string in case linking is
|
||||
/// required and the compiled source has placeholders.
|
||||
pub contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
||||
}
|
||||
|
||||
/// A generic builder style interface for configuring the supported compiler options.
|
||||
#[derive(Default)]
|
||||
pub struct Compiler {
|
||||
input: CompilerInput,
|
||||
input: CompilerInput,
|
||||
}
|
||||
|
||||
impl Compiler {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
input: CompilerInput {
|
||||
pipeline: Default::default(),
|
||||
optimization: Default::default(),
|
||||
evm_version: Default::default(),
|
||||
allow_paths: Default::default(),
|
||||
base_path: Default::default(),
|
||||
sources: Default::default(),
|
||||
libraries: Default::default(),
|
||||
revert_string_handling: Default::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
input: CompilerInput {
|
||||
pipeline: Default::default(),
|
||||
optimization: Default::default(),
|
||||
evm_version: Default::default(),
|
||||
allow_paths: Default::default(),
|
||||
base_path: Default::default(),
|
||||
sources: Default::default(),
|
||||
libraries: Default::default(),
|
||||
revert_string_handling: Default::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_optimization(mut self, value: impl Into<Option<ModeOptimizerSetting>>) -> Self {
|
||||
self.input.optimization = value.into();
|
||||
self
|
||||
}
|
||||
pub fn with_optimization(mut self, value: impl Into<Option<ModeOptimizerSetting>>) -> Self {
|
||||
self.input.optimization = value.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_pipeline(mut self, value: impl Into<Option<ModePipeline>>) -> Self {
|
||||
self.input.pipeline = value.into();
|
||||
self
|
||||
}
|
||||
pub fn with_pipeline(mut self, value: impl Into<Option<ModePipeline>>) -> Self {
|
||||
self.input.pipeline = value.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_evm_version(mut self, version: impl Into<Option<EVMVersion>>) -> Self {
|
||||
self.input.evm_version = version.into();
|
||||
self
|
||||
}
|
||||
pub fn with_evm_version(mut self, version: impl Into<Option<EVMVersion>>) -> Self {
|
||||
self.input.evm_version = version.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_allow_path(mut self, path: impl AsRef<Path>) -> Self {
|
||||
self.input.allow_paths.push(path.as_ref().into());
|
||||
self
|
||||
}
|
||||
pub fn with_allow_path(mut self, path: impl AsRef<Path>) -> Self {
|
||||
self.input.allow_paths.push(path.as_ref().into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_base_path(mut self, path: impl Into<Option<PathBuf>>) -> Self {
|
||||
self.input.base_path = path.into();
|
||||
self
|
||||
}
|
||||
pub fn with_base_path(mut self, path: impl Into<Option<PathBuf>>) -> Self {
|
||||
self.input.base_path = path.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_source(mut self, path: impl AsRef<Path>) -> Result<Self> {
|
||||
self.input.sources.insert(
|
||||
path.as_ref().to_path_buf(),
|
||||
read_to_string(path.as_ref()).context("Failed to read the contract source")?,
|
||||
);
|
||||
Ok(self)
|
||||
}
|
||||
pub fn with_source(mut self, path: impl AsRef<Path>) -> Result<Self> {
|
||||
self.input.sources.insert(
|
||||
path.as_ref().to_path_buf(),
|
||||
read_to_string(path.as_ref()).context("Failed to read the contract source")?,
|
||||
);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn with_library(
|
||||
mut self,
|
||||
path: impl AsRef<Path>,
|
||||
name: impl AsRef<str>,
|
||||
address: Address,
|
||||
) -> Self {
|
||||
self.input
|
||||
.libraries
|
||||
.entry(path.as_ref().to_path_buf())
|
||||
.or_default()
|
||||
.insert(name.as_ref().into(), address);
|
||||
self
|
||||
}
|
||||
pub fn with_library(
|
||||
mut self,
|
||||
path: impl AsRef<Path>,
|
||||
name: impl AsRef<str>,
|
||||
address: Address,
|
||||
) -> Self {
|
||||
self.input
|
||||
.libraries
|
||||
.entry(path.as_ref().to_path_buf())
|
||||
.or_default()
|
||||
.insert(name.as_ref().into(), address);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_revert_string_handling(
|
||||
mut self,
|
||||
revert_string_handling: impl Into<Option<RevertString>>,
|
||||
) -> Self {
|
||||
self.input.revert_string_handling = revert_string_handling.into();
|
||||
self
|
||||
}
|
||||
pub fn with_revert_string_handling(
|
||||
mut self,
|
||||
revert_string_handling: impl Into<Option<RevertString>>,
|
||||
) -> Self {
|
||||
self.input.revert_string_handling = revert_string_handling.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn then(self, callback: impl FnOnce(Self) -> Self) -> Self {
|
||||
callback(self)
|
||||
}
|
||||
pub fn then(self, callback: impl FnOnce(Self) -> Self) -> Self {
|
||||
callback(self)
|
||||
}
|
||||
|
||||
pub fn try_then<E>(self, callback: impl FnOnce(Self) -> Result<Self, E>) -> Result<Self, E> {
|
||||
callback(self)
|
||||
}
|
||||
pub fn try_then<E>(self, callback: impl FnOnce(Self) -> Result<Self, E>) -> Result<Self, E> {
|
||||
callback(self)
|
||||
}
|
||||
|
||||
pub async fn try_build(self, compiler: &dyn SolidityCompiler) -> Result<CompilerOutput> {
|
||||
compiler.build(self.input).await
|
||||
}
|
||||
pub async fn try_build(self, compiler: &dyn SolidityCompiler) -> Result<CompilerOutput> {
|
||||
compiler.build(self.input).await
|
||||
}
|
||||
|
||||
pub fn input(&self) -> &CompilerInput {
|
||||
&self.input
|
||||
}
|
||||
pub fn input(&self) -> &CompilerInput {
|
||||
&self.input
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines how the compiler should handle revert strings.
|
||||
#[derive(
|
||||
Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
|
||||
Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
|
||||
)]
|
||||
pub enum RevertString {
|
||||
#[default]
|
||||
Default,
|
||||
Debug,
|
||||
Strip,
|
||||
VerboseDebug,
|
||||
#[default]
|
||||
Default,
|
||||
Debug,
|
||||
Strip,
|
||||
VerboseDebug,
|
||||
}
|
||||
|
||||
@@ -2,24 +2,24 @@
|
||||
//! compiling contracts to PolkaVM (PVM) bytecode.
|
||||
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
pin::Pin,
|
||||
process::Stdio,
|
||||
sync::{Arc, LazyLock},
|
||||
path::PathBuf,
|
||||
pin::Pin,
|
||||
process::Stdio,
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
|
||||
use dashmap::DashMap;
|
||||
use revive_dt_common::types::VersionOrRequirement;
|
||||
use revive_dt_config::{ResolcConfiguration, SolcConfiguration, WorkingDirectoryConfiguration};
|
||||
use revive_solc_json_interface::{
|
||||
SolcStandardJsonInput, SolcStandardJsonInputLanguage, SolcStandardJsonInputSettings,
|
||||
SolcStandardJsonInputSettingsOptimizer, SolcStandardJsonInputSettingsSelection,
|
||||
SolcStandardJsonOutput,
|
||||
SolcStandardJsonInput, SolcStandardJsonInputLanguage, SolcStandardJsonInputSettings,
|
||||
SolcStandardJsonInputSettingsOptimizer, SolcStandardJsonInputSettingsSelection,
|
||||
SolcStandardJsonOutput,
|
||||
};
|
||||
use tracing::{Span, field::display};
|
||||
|
||||
use crate::{
|
||||
CompilerInput, CompilerOutput, ModeOptimizerSetting, ModePipeline, SolidityCompiler, solc::Solc,
|
||||
CompilerInput, CompilerOutput, ModeOptimizerSetting, ModePipeline, SolidityCompiler, solc::Solc,
|
||||
};
|
||||
|
||||
use alloy::json_abi::JsonAbi;
|
||||
@@ -33,55 +33,52 @@ pub struct Resolc(Arc<ResolcInner>);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
struct ResolcInner {
|
||||
/// The internal solc compiler that the resolc compiler uses as a compiler frontend.
|
||||
solc: Solc,
|
||||
/// Path to the `resolc` executable
|
||||
resolc_path: PathBuf,
|
||||
/// The internal solc compiler that the resolc compiler uses as a compiler frontend.
|
||||
solc: Solc,
|
||||
/// Path to the `resolc` executable
|
||||
resolc_path: PathBuf,
|
||||
}
|
||||
|
||||
impl Resolc {
|
||||
pub async fn new(
|
||||
context: impl AsRef<SolcConfiguration>
|
||||
+ AsRef<ResolcConfiguration>
|
||||
+ AsRef<WorkingDirectoryConfiguration>,
|
||||
version: impl Into<Option<VersionOrRequirement>>,
|
||||
) -> Result<Self> {
|
||||
/// This is a cache of all of the resolc compiler objects. Since we do not currently support
|
||||
/// multiple resolc compiler versions, so our cache is just keyed by the solc compiler and
|
||||
/// its version to the resolc compiler.
|
||||
static COMPILERS_CACHE: LazyLock<DashMap<Solc, Resolc>> = LazyLock::new(Default::default);
|
||||
pub async fn new(
|
||||
context: impl AsRef<SolcConfiguration>
|
||||
+ AsRef<ResolcConfiguration>
|
||||
+ AsRef<WorkingDirectoryConfiguration>,
|
||||
version: impl Into<Option<VersionOrRequirement>>,
|
||||
) -> Result<Self> {
|
||||
/// This is a cache of all of the resolc compiler objects. Since we do not currently support
|
||||
/// multiple resolc compiler versions, so our cache is just keyed by the solc compiler and
|
||||
/// its version to the resolc compiler.
|
||||
static COMPILERS_CACHE: LazyLock<DashMap<Solc, Resolc>> = LazyLock::new(Default::default);
|
||||
|
||||
let resolc_configuration = AsRef::<ResolcConfiguration>::as_ref(&context);
|
||||
let resolc_configuration = AsRef::<ResolcConfiguration>::as_ref(&context);
|
||||
|
||||
let solc = Solc::new(&context, version)
|
||||
.await
|
||||
.context("Failed to create the solc compiler frontend for resolc")?;
|
||||
let solc = Solc::new(&context, version)
|
||||
.await
|
||||
.context("Failed to create the solc compiler frontend for resolc")?;
|
||||
|
||||
Ok(COMPILERS_CACHE
|
||||
.entry(solc.clone())
|
||||
.or_insert_with(|| {
|
||||
Self(Arc::new(ResolcInner {
|
||||
solc,
|
||||
resolc_path: resolc_configuration.path.clone(),
|
||||
}))
|
||||
})
|
||||
.clone())
|
||||
}
|
||||
Ok(COMPILERS_CACHE
|
||||
.entry(solc.clone())
|
||||
.or_insert_with(|| {
|
||||
Self(Arc::new(ResolcInner { solc, resolc_path: resolc_configuration.path.clone() }))
|
||||
})
|
||||
.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl SolidityCompiler for Resolc {
|
||||
fn version(&self) -> &Version {
|
||||
// We currently return the solc compiler version since we do not support multiple resolc
|
||||
// compiler versions.
|
||||
SolidityCompiler::version(&self.0.solc)
|
||||
}
|
||||
fn version(&self) -> &Version {
|
||||
// We currently return the solc compiler version since we do not support multiple resolc
|
||||
// compiler versions.
|
||||
SolidityCompiler::version(&self.0.solc)
|
||||
}
|
||||
|
||||
fn path(&self) -> &std::path::Path {
|
||||
&self.0.resolc_path
|
||||
}
|
||||
fn path(&self) -> &std::path::Path {
|
||||
&self.0.resolc_path
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", ret)]
|
||||
#[tracing::instrument(
|
||||
#[tracing::instrument(level = "debug", ret)]
|
||||
#[tracing::instrument(
|
||||
level = "error",
|
||||
skip_all,
|
||||
fields(
|
||||
@@ -91,221 +88,216 @@ impl SolidityCompiler for Resolc {
|
||||
),
|
||||
err(Debug)
|
||||
)]
|
||||
fn build(
|
||||
&self,
|
||||
CompilerInput {
|
||||
pipeline,
|
||||
optimization,
|
||||
evm_version,
|
||||
allow_paths,
|
||||
base_path,
|
||||
sources,
|
||||
libraries,
|
||||
// TODO: this is currently not being handled since there is no way to pass it into
|
||||
// resolc. So, we need to go back to this later once it's supported.
|
||||
revert_string_handling: _,
|
||||
}: CompilerInput,
|
||||
) -> Pin<Box<dyn Future<Output = Result<CompilerOutput>> + '_>> {
|
||||
Box::pin(async move {
|
||||
if !matches!(pipeline, None | Some(ModePipeline::ViaYulIR)) {
|
||||
anyhow::bail!(
|
||||
"Resolc only supports the Y (via Yul IR) pipeline, but the provided pipeline is {pipeline:?}"
|
||||
);
|
||||
}
|
||||
fn build(
|
||||
&self,
|
||||
CompilerInput {
|
||||
pipeline,
|
||||
optimization,
|
||||
evm_version,
|
||||
allow_paths,
|
||||
base_path,
|
||||
sources,
|
||||
libraries,
|
||||
// TODO: this is currently not being handled since there is no way to pass it into
|
||||
// resolc. So, we need to go back to this later once it's supported.
|
||||
revert_string_handling: _,
|
||||
}: CompilerInput,
|
||||
) -> Pin<Box<dyn Future<Output = Result<CompilerOutput>> + '_>> {
|
||||
Box::pin(async move {
|
||||
if !matches!(pipeline, None | Some(ModePipeline::ViaYulIR)) {
|
||||
anyhow::bail!(
|
||||
"Resolc only supports the Y (via Yul IR) pipeline, but the provided pipeline is {pipeline:?}"
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
optimization
|
||||
.unwrap_or(ModeOptimizerSetting::M0)
|
||||
.optimizations_enabled(),
|
||||
None,
|
||||
&Version::new(0, 0, 0),
|
||||
false,
|
||||
),
|
||||
metadata: None,
|
||||
polkavm: None,
|
||||
},
|
||||
};
|
||||
Span::current().record("json_in", display(serde_json::to_string(&input).unwrap()));
|
||||
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(
|
||||
optimization.unwrap_or(ModeOptimizerSetting::M0).optimizations_enabled(),
|
||||
None,
|
||||
&Version::new(0, 0, 0),
|
||||
false,
|
||||
),
|
||||
metadata: None,
|
||||
polkavm: None,
|
||||
},
|
||||
};
|
||||
Span::current().record("json_in", display(serde_json::to_string(&input).unwrap()));
|
||||
|
||||
let path = &self.0.resolc_path;
|
||||
let mut command = AsyncCommand::new(path);
|
||||
command
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.arg("--solc")
|
||||
.arg(self.0.solc.path())
|
||||
.arg("--standard-json");
|
||||
let path = &self.0.resolc_path;
|
||||
let mut command = AsyncCommand::new(path);
|
||||
command
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.arg("--solc")
|
||||
.arg(self.0.solc.path())
|
||||
.arg("--standard-json");
|
||||
|
||||
if let Some(ref base_path) = base_path {
|
||||
command.arg("--base-path").arg(base_path);
|
||||
}
|
||||
if !allow_paths.is_empty() {
|
||||
command.arg("--allow-paths").arg(
|
||||
allow_paths
|
||||
.iter()
|
||||
.map(|path| path.display().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(","),
|
||||
);
|
||||
}
|
||||
let mut child = command
|
||||
.spawn()
|
||||
.with_context(|| format!("Failed to spawn resolc at {}", path.display()))?;
|
||||
if let Some(ref base_path) = base_path {
|
||||
command.arg("--base-path").arg(base_path);
|
||||
}
|
||||
if !allow_paths.is_empty() {
|
||||
command.arg("--allow-paths").arg(
|
||||
allow_paths
|
||||
.iter()
|
||||
.map(|path| path.display().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(","),
|
||||
);
|
||||
}
|
||||
let mut child = command
|
||||
.spawn()
|
||||
.with_context(|| format!("Failed to spawn resolc at {}", path.display()))?;
|
||||
|
||||
let stdin_pipe = child.stdin.as_mut().expect("stdin must be piped");
|
||||
let serialized_input = serde_json::to_vec(&input)
|
||||
.context("Failed to serialize Standard JSON input for resolc")?;
|
||||
stdin_pipe
|
||||
.write_all(&serialized_input)
|
||||
.await
|
||||
.context("Failed to write Standard JSON to resolc stdin")?;
|
||||
let stdin_pipe = child.stdin.as_mut().expect("stdin must be piped");
|
||||
let serialized_input = serde_json::to_vec(&input)
|
||||
.context("Failed to serialize Standard JSON input for resolc")?;
|
||||
stdin_pipe
|
||||
.write_all(&serialized_input)
|
||||
.await
|
||||
.context("Failed to write Standard JSON to resolc stdin")?;
|
||||
|
||||
let output = child
|
||||
.wait_with_output()
|
||||
.await
|
||||
.context("Failed while waiting for resolc process to finish")?;
|
||||
let stdout = output.stdout;
|
||||
let stderr = output.stderr;
|
||||
let output = child
|
||||
.wait_with_output()
|
||||
.await
|
||||
.context("Failed while waiting for resolc process to finish")?;
|
||||
let stdout = output.stdout;
|
||||
let stderr = output.stderr;
|
||||
|
||||
if !output.status.success() {
|
||||
let json_in = serde_json::to_string_pretty(&input)
|
||||
.context("Failed to pretty-print Standard JSON input for logging")?;
|
||||
let message = String::from_utf8_lossy(&stderr);
|
||||
tracing::error!(
|
||||
status = %output.status,
|
||||
message = %message,
|
||||
json_input = json_in,
|
||||
"Compilation using resolc failed"
|
||||
);
|
||||
anyhow::bail!("Compilation failed with an error: {message}");
|
||||
}
|
||||
if !output.status.success() {
|
||||
let json_in = serde_json::to_string_pretty(&input)
|
||||
.context("Failed to pretty-print Standard JSON input for logging")?;
|
||||
let message = String::from_utf8_lossy(&stderr);
|
||||
tracing::error!(
|
||||
status = %output.status,
|
||||
message = %message,
|
||||
json_input = json_in,
|
||||
"Compilation using resolc failed"
|
||||
);
|
||||
anyhow::bail!("Compilation failed with an error: {message}");
|
||||
}
|
||||
|
||||
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)
|
||||
)
|
||||
})
|
||||
.context("Failed to parse resolc standard JSON output")?;
|
||||
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)
|
||||
)
|
||||
})
|
||||
.context("Failed to parse resolc standard JSON output")?;
|
||||
|
||||
tracing::debug!(
|
||||
output = %serde_json::to_string(&parsed).unwrap(),
|
||||
"Compiled successfully"
|
||||
);
|
||||
tracing::debug!(
|
||||
output = %serde_json::to_string(&parsed).unwrap(),
|
||||
"Compiled successfully"
|
||||
);
|
||||
|
||||
// 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}")
|
||||
}
|
||||
}
|
||||
// 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 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 src_for_msg = source_path.clone();
|
||||
let source_path = PathBuf::from(source_path)
|
||||
.canonicalize()
|
||||
.with_context(|| format!("Failed to canonicalize path {src_for_msg}"))?;
|
||||
let mut compiler_output = CompilerOutput::default();
|
||||
for (source_path, contracts) in contracts.into_iter() {
|
||||
let src_for_msg = source_path.clone();
|
||||
let source_path = PathBuf::from(source_path)
|
||||
.canonicalize()
|
||||
.with_context(|| format!("Failed to canonicalize path {src_for_msg}"))?;
|
||||
|
||||
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 = {
|
||||
let metadata = contract_information
|
||||
.metadata
|
||||
.as_ref()
|
||||
.context("No metadata found for the contract")?;
|
||||
let solc_metadata_str = match metadata {
|
||||
serde_json::Value::String(solc_metadata_str) => {
|
||||
solc_metadata_str.as_str()
|
||||
}
|
||||
serde_json::Value::Object(metadata_object) => {
|
||||
let solc_metadata_value = metadata_object
|
||||
.get("solc_metadata")
|
||||
.context("Contract doesn't have a 'solc_metadata' field")?;
|
||||
solc_metadata_value
|
||||
.as_str()
|
||||
.context("The 'solc_metadata' field is not a string")?
|
||||
}
|
||||
serde_json::Value::Null
|
||||
| serde_json::Value::Bool(_)
|
||||
| serde_json::Value::Number(_)
|
||||
| serde_json::Value::Array(_) => {
|
||||
anyhow::bail!("Unsupported type of metadata {metadata:?}")
|
||||
}
|
||||
};
|
||||
let solc_metadata = serde_json::from_str::<serde_json::Value>(
|
||||
solc_metadata_str,
|
||||
)
|
||||
.context(
|
||||
"Failed to deserialize the solc_metadata as a serde_json generic value",
|
||||
)?;
|
||||
let output_value = solc_metadata
|
||||
.get("output")
|
||||
.context("solc_metadata doesn't have an output field")?;
|
||||
let abi_value = output_value
|
||||
.get("abi")
|
||||
.context("solc_metadata output doesn't contain an abi field")?;
|
||||
serde_json::from_value::<JsonAbi>(abi_value.clone())
|
||||
.context("ABI found in solc_metadata output is not valid ABI")?
|
||||
};
|
||||
map.insert(contract_name, (bytecode.object, abi));
|
||||
}
|
||||
}
|
||||
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 = {
|
||||
let metadata = contract_information
|
||||
.metadata
|
||||
.as_ref()
|
||||
.context("No metadata found for the contract")?;
|
||||
let solc_metadata_str = match metadata {
|
||||
serde_json::Value::String(solc_metadata_str) =>
|
||||
solc_metadata_str.as_str(),
|
||||
serde_json::Value::Object(metadata_object) => {
|
||||
let solc_metadata_value = metadata_object
|
||||
.get("solc_metadata")
|
||||
.context("Contract doesn't have a 'solc_metadata' field")?;
|
||||
solc_metadata_value
|
||||
.as_str()
|
||||
.context("The 'solc_metadata' field is not a string")?
|
||||
},
|
||||
serde_json::Value::Null |
|
||||
serde_json::Value::Bool(_) |
|
||||
serde_json::Value::Number(_) |
|
||||
serde_json::Value::Array(_) => {
|
||||
anyhow::bail!("Unsupported type of metadata {metadata:?}")
|
||||
},
|
||||
};
|
||||
let solc_metadata =
|
||||
serde_json::from_str::<serde_json::Value>(solc_metadata_str).context(
|
||||
"Failed to deserialize the solc_metadata as a serde_json generic value",
|
||||
)?;
|
||||
let output_value = solc_metadata
|
||||
.get("output")
|
||||
.context("solc_metadata doesn't have an output field")?;
|
||||
let abi_value = output_value
|
||||
.get("abi")
|
||||
.context("solc_metadata output doesn't contain an abi field")?;
|
||||
serde_json::from_value::<JsonAbi>(abi_value.clone())
|
||||
.context("ABI found in solc_metadata output is not valid ABI")?
|
||||
};
|
||||
map.insert(contract_name, (bytecode.object, abi));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(compiler_output)
|
||||
})
|
||||
}
|
||||
Ok(compiler_output)
|
||||
})
|
||||
}
|
||||
|
||||
fn supports_mode(
|
||||
&self,
|
||||
optimize_setting: ModeOptimizerSetting,
|
||||
pipeline: ModePipeline,
|
||||
) -> bool {
|
||||
pipeline == ModePipeline::ViaYulIR
|
||||
&& SolidityCompiler::supports_mode(&self.0.solc, optimize_setting, pipeline)
|
||||
}
|
||||
fn supports_mode(
|
||||
&self,
|
||||
optimize_setting: ModeOptimizerSetting,
|
||||
pipeline: ModePipeline,
|
||||
) -> bool {
|
||||
pipeline == ModePipeline::ViaYulIR &&
|
||||
SolidityCompiler::supports_mode(&self.0.solc, optimize_setting, pipeline)
|
||||
}
|
||||
}
|
||||
|
||||
+234
-242
@@ -2,10 +2,10 @@
|
||||
//! compiling contracts to EVM bytecode.
|
||||
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
pin::Pin,
|
||||
process::Stdio,
|
||||
sync::{Arc, LazyLock},
|
||||
path::PathBuf,
|
||||
pin::Pin,
|
||||
process::Stdio,
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
|
||||
use dashmap::DashMap;
|
||||
@@ -18,11 +18,10 @@ use crate::{CompilerInput, CompilerOutput, ModeOptimizerSetting, ModePipeline, S
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use foundry_compilers_artifacts::{
|
||||
output_selection::{
|
||||
BytecodeOutputSelection, ContractOutputSelection, EvmOutputSelection, OutputSelection,
|
||||
},
|
||||
solc::CompilerOutput as SolcOutput,
|
||||
solc::*,
|
||||
output_selection::{
|
||||
BytecodeOutputSelection, ContractOutputSelection, EvmOutputSelection, OutputSelection,
|
||||
},
|
||||
solc::{CompilerOutput as SolcOutput, *},
|
||||
};
|
||||
use semver::Version;
|
||||
use tokio::{io::AsyncWriteExt, process::Command as AsyncCommand};
|
||||
@@ -32,268 +31,261 @@ pub struct Solc(Arc<SolcInner>);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
struct SolcInner {
|
||||
/// The path of the solidity compiler executable that this object uses.
|
||||
solc_path: PathBuf,
|
||||
/// The version of the solidity compiler executable that this object uses.
|
||||
solc_version: Version,
|
||||
/// The path of the solidity compiler executable that this object uses.
|
||||
solc_path: PathBuf,
|
||||
/// The version of the solidity compiler executable that this object uses.
|
||||
solc_version: Version,
|
||||
}
|
||||
|
||||
impl Solc {
|
||||
pub async fn new(
|
||||
context: impl AsRef<SolcConfiguration> + AsRef<WorkingDirectoryConfiguration>,
|
||||
version: impl Into<Option<VersionOrRequirement>>,
|
||||
) -> Result<Self> {
|
||||
// This is a cache for the compiler objects so that whenever the same compiler version is
|
||||
// requested the same object is returned. We do this as we do not want to keep cloning the
|
||||
// compiler around.
|
||||
static COMPILERS_CACHE: LazyLock<DashMap<(PathBuf, Version), Solc>> =
|
||||
LazyLock::new(Default::default);
|
||||
pub async fn new(
|
||||
context: impl AsRef<SolcConfiguration> + AsRef<WorkingDirectoryConfiguration>,
|
||||
version: impl Into<Option<VersionOrRequirement>>,
|
||||
) -> Result<Self> {
|
||||
// This is a cache for the compiler objects so that whenever the same compiler version is
|
||||
// requested the same object is returned. We do this as we do not want to keep cloning the
|
||||
// compiler around.
|
||||
static COMPILERS_CACHE: LazyLock<DashMap<(PathBuf, Version), Solc>> =
|
||||
LazyLock::new(Default::default);
|
||||
|
||||
let working_directory_configuration =
|
||||
AsRef::<WorkingDirectoryConfiguration>::as_ref(&context);
|
||||
let solc_configuration = AsRef::<SolcConfiguration>::as_ref(&context);
|
||||
let working_directory_configuration =
|
||||
AsRef::<WorkingDirectoryConfiguration>::as_ref(&context);
|
||||
let solc_configuration = AsRef::<SolcConfiguration>::as_ref(&context);
|
||||
|
||||
// We attempt to download the solc binary. Note the following: this call does the version
|
||||
// resolution for us. Therefore, even if the download didn't proceed, this function will
|
||||
// resolve the version requirement into a canonical version of the compiler. It's then up
|
||||
// to us to either use the provided path or not.
|
||||
let version = version
|
||||
.into()
|
||||
.unwrap_or_else(|| solc_configuration.version.clone().into());
|
||||
let (version, path) =
|
||||
download_solc(working_directory_configuration.as_path(), version, false)
|
||||
.await
|
||||
.context("Failed to download/get path to solc binary")?;
|
||||
// We attempt to download the solc binary. Note the following: this call does the version
|
||||
// resolution for us. Therefore, even if the download didn't proceed, this function will
|
||||
// resolve the version requirement into a canonical version of the compiler. It's then up
|
||||
// to us to either use the provided path or not.
|
||||
let version = version.into().unwrap_or_else(|| solc_configuration.version.clone().into());
|
||||
let (version, path) =
|
||||
download_solc(working_directory_configuration.as_path(), version, false)
|
||||
.await
|
||||
.context("Failed to download/get path to solc binary")?;
|
||||
|
||||
Ok(COMPILERS_CACHE
|
||||
.entry((path.clone(), version.clone()))
|
||||
.or_insert_with(|| {
|
||||
info!(
|
||||
solc_path = %path.display(),
|
||||
solc_version = %version,
|
||||
"Created a new solc compiler object"
|
||||
);
|
||||
Self(Arc::new(SolcInner {
|
||||
solc_path: path,
|
||||
solc_version: version,
|
||||
}))
|
||||
})
|
||||
.clone())
|
||||
}
|
||||
Ok(COMPILERS_CACHE
|
||||
.entry((path.clone(), version.clone()))
|
||||
.or_insert_with(|| {
|
||||
info!(
|
||||
solc_path = %path.display(),
|
||||
solc_version = %version,
|
||||
"Created a new solc compiler object"
|
||||
);
|
||||
Self(Arc::new(SolcInner { solc_path: path, solc_version: version }))
|
||||
})
|
||||
.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl SolidityCompiler for Solc {
|
||||
fn version(&self) -> &Version {
|
||||
&self.0.solc_version
|
||||
}
|
||||
fn version(&self) -> &Version {
|
||||
&self.0.solc_version
|
||||
}
|
||||
|
||||
fn path(&self) -> &std::path::Path {
|
||||
&self.0.solc_path
|
||||
}
|
||||
fn path(&self) -> &std::path::Path {
|
||||
&self.0.solc_path
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", ret)]
|
||||
#[tracing::instrument(
|
||||
#[tracing::instrument(level = "debug", ret)]
|
||||
#[tracing::instrument(
|
||||
level = "error",
|
||||
skip_all,
|
||||
fields(json_in = tracing::field::Empty),
|
||||
err(Debug)
|
||||
)]
|
||||
fn build(
|
||||
&self,
|
||||
CompilerInput {
|
||||
pipeline,
|
||||
optimization,
|
||||
evm_version,
|
||||
allow_paths,
|
||||
base_path,
|
||||
sources,
|
||||
libraries,
|
||||
revert_string_handling,
|
||||
}: CompilerInput,
|
||||
) -> Pin<Box<dyn Future<Output = Result<CompilerOutput>> + '_>> {
|
||||
Box::pin(async move {
|
||||
// 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
|
||||
// `supports_mode` is called prior to instantiating a compiler, we should never
|
||||
// ask for something which is invalid.
|
||||
let via_ir = match (pipeline, self.compiler_supports_yul()) {
|
||||
(pipeline, true) => pipeline.map(|p| p.via_yul_ir()),
|
||||
(_pipeline, false) => None,
|
||||
};
|
||||
fn build(
|
||||
&self,
|
||||
CompilerInput {
|
||||
pipeline,
|
||||
optimization,
|
||||
evm_version,
|
||||
allow_paths,
|
||||
base_path,
|
||||
sources,
|
||||
libraries,
|
||||
revert_string_handling,
|
||||
}: CompilerInput,
|
||||
) -> Pin<Box<dyn Future<Output = Result<CompilerOutput>> + '_>> {
|
||||
Box::pin(async move {
|
||||
// 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
|
||||
// `supports_mode` is called prior to instantiating a compiler, we should never
|
||||
// ask for something which is invalid.
|
||||
let via_ir = match (pipeline, self.compiler_supports_yul()) {
|
||||
(pipeline, true) => pipeline.map(|p| p.via_yul_ir()),
|
||||
(_pipeline, false) => None,
|
||||
};
|
||||
|
||||
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: optimization.map(|o| o.optimizations_enabled()),
|
||||
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(),
|
||||
},
|
||||
debug: revert_string_handling.map(|revert_string_handling| DebuggingSettings {
|
||||
revert_strings: match revert_string_handling {
|
||||
crate::RevertString::Default => Some(RevertStrings::Default),
|
||||
crate::RevertString::Debug => Some(RevertStrings::Debug),
|
||||
crate::RevertString::Strip => Some(RevertStrings::Strip),
|
||||
crate::RevertString::VerboseDebug => Some(RevertStrings::VerboseDebug),
|
||||
},
|
||||
debug_info: Default::default(),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
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: optimization.map(|o| o.optimizations_enabled()),
|
||||
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(),
|
||||
},
|
||||
debug: revert_string_handling.map(|revert_string_handling| DebuggingSettings {
|
||||
revert_strings: match revert_string_handling {
|
||||
crate::RevertString::Default => Some(RevertStrings::Default),
|
||||
crate::RevertString::Debug => Some(RevertStrings::Debug),
|
||||
crate::RevertString::Strip => Some(RevertStrings::Strip),
|
||||
crate::RevertString::VerboseDebug => Some(RevertStrings::VerboseDebug),
|
||||
},
|
||||
debug_info: Default::default(),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
|
||||
Span::current().record("json_in", display(serde_json::to_string(&input).unwrap()));
|
||||
Span::current().record("json_in", display(serde_json::to_string(&input).unwrap()));
|
||||
|
||||
let path = &self.0.solc_path;
|
||||
let mut command = AsyncCommand::new(path);
|
||||
command
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::null())
|
||||
.arg("--standard-json");
|
||||
let path = &self.0.solc_path;
|
||||
let mut command = AsyncCommand::new(path);
|
||||
command
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::null())
|
||||
.arg("--standard-json");
|
||||
|
||||
if let Some(ref base_path) = base_path {
|
||||
command.arg("--base-path").arg(base_path);
|
||||
}
|
||||
if !allow_paths.is_empty() {
|
||||
command.arg("--allow-paths").arg(
|
||||
allow_paths
|
||||
.iter()
|
||||
.map(|path| path.display().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(","),
|
||||
);
|
||||
}
|
||||
let mut child = command
|
||||
.spawn()
|
||||
.with_context(|| format!("Failed to spawn solc at {}", path.display()))?;
|
||||
if let Some(ref base_path) = base_path {
|
||||
command.arg("--base-path").arg(base_path);
|
||||
}
|
||||
if !allow_paths.is_empty() {
|
||||
command.arg("--allow-paths").arg(
|
||||
allow_paths
|
||||
.iter()
|
||||
.map(|path| path.display().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(","),
|
||||
);
|
||||
}
|
||||
let mut child = command
|
||||
.spawn()
|
||||
.with_context(|| format!("Failed to spawn solc at {}", path.display()))?;
|
||||
|
||||
let stdin = child.stdin.as_mut().expect("should be piped");
|
||||
let serialized_input = serde_json::to_vec(&input)
|
||||
.context("Failed to serialize Standard JSON input for solc")?;
|
||||
stdin
|
||||
.write_all(&serialized_input)
|
||||
.await
|
||||
.context("Failed to write Standard JSON to solc stdin")?;
|
||||
let output = child
|
||||
.wait_with_output()
|
||||
.await
|
||||
.context("Failed while waiting for solc process to finish")?;
|
||||
let stdin = child.stdin.as_mut().expect("should be piped");
|
||||
let serialized_input = serde_json::to_vec(&input)
|
||||
.context("Failed to serialize Standard JSON input for solc")?;
|
||||
stdin
|
||||
.write_all(&serialized_input)
|
||||
.await
|
||||
.context("Failed to write Standard JSON to solc stdin")?;
|
||||
let output = child
|
||||
.wait_with_output()
|
||||
.await
|
||||
.context("Failed while waiting for solc process to finish")?;
|
||||
|
||||
if !output.status.success() {
|
||||
let json_in = serde_json::to_string_pretty(&input)
|
||||
.context("Failed to pretty-print Standard JSON input for logging")?;
|
||||
tracing::error!(
|
||||
status = %output.status,
|
||||
json_input = json_in,
|
||||
"Compilation using solc failed"
|
||||
);
|
||||
anyhow::bail!("Compilation failed");
|
||||
}
|
||||
if !output.status.success() {
|
||||
let json_in = serde_json::to_string_pretty(&input)
|
||||
.context("Failed to pretty-print Standard JSON input for logging")?;
|
||||
tracing::error!(
|
||||
status = %output.status,
|
||||
json_input = json_in,
|
||||
"Compilation using solc failed"
|
||||
);
|
||||
anyhow::bail!("Compilation failed");
|
||||
}
|
||||
|
||||
let parsed = serde_json::from_slice::<SolcOutput>(&output.stdout)
|
||||
.map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to parse resolc JSON output: {e}\nstdout: {}",
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
)
|
||||
})
|
||||
.context("Failed to parse solc standard JSON output")?;
|
||||
let parsed = serde_json::from_slice::<SolcOutput>(&output.stdout)
|
||||
.map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to parse resolc JSON output: {e}\nstdout: {}",
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
)
|
||||
})
|
||||
.context("Failed to parse solc standard JSON output")?;
|
||||
|
||||
// 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() {
|
||||
if error.severity == Severity::Error {
|
||||
tracing::error!(?error, ?input, "Encountered an error in the compilation");
|
||||
anyhow::bail!("Encountered an error in the compilation: {error}")
|
||||
}
|
||||
}
|
||||
// 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() {
|
||||
if error.severity == Severity::Error {
|
||||
tracing::error!(?error, ?input, "Encountered an error in the compilation");
|
||||
anyhow::bail!("Encountered an error in the compilation: {error}")
|
||||
}
|
||||
}
|
||||
|
||||
tracing::debug!(
|
||||
output = %String::from_utf8_lossy(&output.stdout).to_string(),
|
||||
"Compiled successfully"
|
||||
);
|
||||
tracing::debug!(
|
||||
output = %String::from_utf8_lossy(&output.stdout).to_string(),
|
||||
"Compiled successfully"
|
||||
);
|
||||
|
||||
let mut compiler_output = CompilerOutput::default();
|
||||
for (contract_path, contracts) in parsed.contracts {
|
||||
let map = compiler_output
|
||||
.contracts
|
||||
.entry(contract_path.canonicalize().with_context(|| {
|
||||
format!(
|
||||
"Failed to canonicalize contract path {}",
|
||||
contract_path.display()
|
||||
)
|
||||
})?)
|
||||
.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));
|
||||
}
|
||||
}
|
||||
let mut compiler_output = CompilerOutput::default();
|
||||
for (contract_path, contracts) in parsed.contracts {
|
||||
let map = compiler_output
|
||||
.contracts
|
||||
.entry(contract_path.canonicalize().with_context(|| {
|
||||
format!("Failed to canonicalize contract path {}", contract_path.display())
|
||||
})?)
|
||||
.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)
|
||||
})
|
||||
}
|
||||
Ok(compiler_output)
|
||||
})
|
||||
}
|
||||
|
||||
fn supports_mode(
|
||||
&self,
|
||||
_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 && self.compiler_supports_yul())
|
||||
}
|
||||
fn supports_mode(
|
||||
&self,
|
||||
_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 && self.compiler_supports_yul())
|
||||
}
|
||||
}
|
||||
|
||||
impl Solc {
|
||||
fn compiler_supports_yul(&self) -> bool {
|
||||
const SOLC_VERSION_SUPPORTING_VIA_YUL_IR: Version = Version::new(0, 8, 13);
|
||||
SolidityCompiler::version(self) >= &SOLC_VERSION_SUPPORTING_VIA_YUL_IR
|
||||
}
|
||||
fn compiler_supports_yul(&self) -> bool {
|
||||
const SOLC_VERSION_SUPPORTING_VIA_YUL_IR: Version = Version::new(0, 8, 13);
|
||||
SolidityCompiler::version(self) >= &SOLC_VERSION_SUPPORTING_VIA_YUL_IR
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,82 +7,82 @@ use semver::Version;
|
||||
|
||||
#[tokio::test]
|
||||
async fn contracts_can_be_compiled_with_solc() {
|
||||
// Arrange
|
||||
let args = TestExecutionContext::default();
|
||||
let solc = Solc::new(&args, VersionOrRequirement::Version(Version::new(0, 8, 30)))
|
||||
.await
|
||||
.unwrap();
|
||||
// Arrange
|
||||
let args = TestExecutionContext::default();
|
||||
let solc = Solc::new(&args, VersionOrRequirement::Version(Version::new(0, 8, 30)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Act
|
||||
let output = Compiler::new()
|
||||
.with_source("./tests/assets/array_one_element/callable.sol")
|
||||
.unwrap()
|
||||
.with_source("./tests/assets/array_one_element/main.sol")
|
||||
.unwrap()
|
||||
.try_build(&solc)
|
||||
.await;
|
||||
// Act
|
||||
let output = Compiler::new()
|
||||
.with_source("./tests/assets/array_one_element/callable.sol")
|
||||
.unwrap()
|
||||
.with_source("./tests/assets/array_one_element/main.sol")
|
||||
.unwrap()
|
||||
.try_build(&solc)
|
||||
.await;
|
||||
|
||||
// Assert
|
||||
let output = output.expect("Failed to compile");
|
||||
assert_eq!(output.contracts.len(), 2);
|
||||
// Assert
|
||||
let output = output.expect("Failed to compile");
|
||||
assert_eq!(output.contracts.len(), 2);
|
||||
|
||||
let main_file_contracts = output
|
||||
.contracts
|
||||
.get(
|
||||
&PathBuf::from("./tests/assets/array_one_element/main.sol")
|
||||
.canonicalize()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let callable_file_contracts = output
|
||||
.contracts
|
||||
.get(
|
||||
&PathBuf::from("./tests/assets/array_one_element/callable.sol")
|
||||
.canonicalize()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(main_file_contracts.contains_key("Main"));
|
||||
assert!(callable_file_contracts.contains_key("Callable"));
|
||||
let main_file_contracts = output
|
||||
.contracts
|
||||
.get(
|
||||
&PathBuf::from("./tests/assets/array_one_element/main.sol")
|
||||
.canonicalize()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let callable_file_contracts = output
|
||||
.contracts
|
||||
.get(
|
||||
&PathBuf::from("./tests/assets/array_one_element/callable.sol")
|
||||
.canonicalize()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(main_file_contracts.contains_key("Main"));
|
||||
assert!(callable_file_contracts.contains_key("Callable"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn contracts_can_be_compiled_with_resolc() {
|
||||
// Arrange
|
||||
let args = TestExecutionContext::default();
|
||||
let resolc = Resolc::new(&args, VersionOrRequirement::Version(Version::new(0, 8, 30)))
|
||||
.await
|
||||
.unwrap();
|
||||
// Arrange
|
||||
let args = TestExecutionContext::default();
|
||||
let resolc = Resolc::new(&args, VersionOrRequirement::Version(Version::new(0, 8, 30)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Act
|
||||
let output = Compiler::new()
|
||||
.with_source("./tests/assets/array_one_element/callable.sol")
|
||||
.unwrap()
|
||||
.with_source("./tests/assets/array_one_element/main.sol")
|
||||
.unwrap()
|
||||
.try_build(&resolc)
|
||||
.await;
|
||||
// Act
|
||||
let output = Compiler::new()
|
||||
.with_source("./tests/assets/array_one_element/callable.sol")
|
||||
.unwrap()
|
||||
.with_source("./tests/assets/array_one_element/main.sol")
|
||||
.unwrap()
|
||||
.try_build(&resolc)
|
||||
.await;
|
||||
|
||||
// Assert
|
||||
let output = output.expect("Failed to compile");
|
||||
assert_eq!(output.contracts.len(), 2);
|
||||
// Assert
|
||||
let output = output.expect("Failed to compile");
|
||||
assert_eq!(output.contracts.len(), 2);
|
||||
|
||||
let main_file_contracts = output
|
||||
.contracts
|
||||
.get(
|
||||
&PathBuf::from("./tests/assets/array_one_element/main.sol")
|
||||
.canonicalize()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let callable_file_contracts = output
|
||||
.contracts
|
||||
.get(
|
||||
&PathBuf::from("./tests/assets/array_one_element/callable.sol")
|
||||
.canonicalize()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(main_file_contracts.contains_key("Main"));
|
||||
assert!(callable_file_contracts.contains_key("Callable"));
|
||||
let main_file_contracts = output
|
||||
.contracts
|
||||
.get(
|
||||
&PathBuf::from("./tests/assets/array_one_element/main.sol")
|
||||
.canonicalize()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let callable_file_contracts = output
|
||||
.contracts
|
||||
.get(
|
||||
&PathBuf::from("./tests/assets/array_one_element/callable.sol")
|
||||
.canonicalize()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(main_file_contracts.contains_key("Main"));
|
||||
assert!(callable_file_contracts.contains_key("Callable"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user