Compare commits

...

3 Commits

Author SHA1 Message Date
Omar Abdulla a0006cb9a8 Set the base path if specified 2025-07-16 00:55:53 +03:00
Omar Abdulla fd2e004ee6 Fix compilation errors related to paths 2025-07-16 00:54:17 +03:00
Omar Abdulla 94cfa8cbcf Compile all contracts for a test file 2025-07-15 19:49:22 +03:00
5 changed files with 168 additions and 27 deletions
+8 -4
View File
@@ -44,6 +44,8 @@ pub trait SolidityCompiler {
pub struct CompilerInput<T: PartialEq + Eq + Hash> { pub struct CompilerInput<T: PartialEq + Eq + Hash> {
pub extra_options: T, pub extra_options: T,
pub input: SolcStandardJsonInput, pub input: SolcStandardJsonInput,
pub allow_paths: Vec<PathBuf>,
pub base_path: Option<PathBuf>,
} }
/// The generic compilation output configuration. /// The generic compilation output configuration.
@@ -83,8 +85,8 @@ where
pub struct Compiler<T: SolidityCompiler> { pub struct Compiler<T: SolidityCompiler> {
input: SolcStandardJsonInput, input: SolcStandardJsonInput,
extra_options: T::Options, extra_options: T::Options,
allow_paths: Vec<String>, allow_paths: Vec<PathBuf>,
base_path: Option<String>, base_path: Option<PathBuf>,
} }
impl Default for Compiler<solc::Solc> { impl Default for Compiler<solc::Solc> {
@@ -145,12 +147,12 @@ where
self self
} }
pub fn allow_path(mut self, path: String) -> Self { pub fn allow_path(mut self, path: PathBuf) -> Self {
self.allow_paths.push(path); self.allow_paths.push(path);
self self
} }
pub fn base_path(mut self, base_path: String) -> Self { pub fn base_path(mut self, base_path: PathBuf) -> Self {
self.base_path = Some(base_path); self.base_path = Some(base_path);
self self
} }
@@ -159,6 +161,8 @@ where
T::new(solc_path).build(CompilerInput { T::new(solc_path).build(CompilerInput {
extra_options: self.extra_options, extra_options: self.extra_options,
input: self.input, input: self.input,
allow_paths: self.allow_paths,
base_path: self.base_path,
}) })
} }
+28 -5
View File
@@ -23,13 +23,27 @@ impl SolidityCompiler for Resolc {
&self, &self,
input: CompilerInput<Self::Options>, input: CompilerInput<Self::Options>,
) -> anyhow::Result<CompilerOutput<Self::Options>> { ) -> anyhow::Result<CompilerOutput<Self::Options>> {
let mut child = Command::new(&self.resolc_path) let mut command = Command::new(&self.resolc_path);
.arg("--standard-json") command
.args(&input.extra_options)
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stderr(Stdio::piped()) .stderr(Stdio::piped())
.spawn()?; .arg("--standard-json");
if let Some(ref base_path) = input.base_path {
command.arg("--base-path").arg(base_path);
}
if !input.allow_paths.is_empty() {
command.arg("--allow-paths").arg(
input
.allow_paths
.iter()
.map(|path| path.display().to_string())
.collect::<Vec<_>>()
.join(","),
);
}
let mut child = command.spawn()?;
let stdin_pipe = child.stdin.as_mut().expect("stdin must be piped"); let stdin_pipe = child.stdin.as_mut().expect("stdin must be piped");
serde_json::to_writer(stdin_pipe, &input.input)?; serde_json::to_writer(stdin_pipe, &input.input)?;
@@ -55,13 +69,22 @@ impl SolidityCompiler for Resolc {
}); });
} }
let parsed: SolcStandardJsonOutput = serde_json::from_slice(&stdout).map_err(|e| { let parsed = serde_json::from_slice::<SolcStandardJsonOutput>(&stdout).map_err(|e| {
anyhow::anyhow!( anyhow::anyhow!(
"failed to parse resolc JSON output: {e}\nstderr: {}", "failed to parse resolc JSON output: {e}\nstderr: {}",
String::from_utf8_lossy(&stderr) 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}")
}
}
Ok(CompilerOutput { Ok(CompilerOutput {
input, input,
output: parsed, output: parsed,
+37 -4
View File
@@ -9,6 +9,7 @@ use std::{
use crate::{CompilerInput, CompilerOutput, SolidityCompiler}; use crate::{CompilerInput, CompilerOutput, SolidityCompiler};
use revive_dt_config::Arguments; use revive_dt_config::Arguments;
use revive_dt_solc_binaries::download_solc; use revive_dt_solc_binaries::download_solc;
use revive_solc_json_interface::SolcStandardJsonOutput;
pub struct Solc { pub struct Solc {
solc_path: PathBuf, solc_path: PathBuf,
@@ -21,12 +22,27 @@ impl SolidityCompiler for Solc {
&self, &self,
input: CompilerInput<Self::Options>, input: CompilerInput<Self::Options>,
) -> anyhow::Result<CompilerOutput<Self::Options>> { ) -> anyhow::Result<CompilerOutput<Self::Options>> {
let mut child = Command::new(&self.solc_path) let mut command = Command::new(&self.solc_path);
command
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stderr(Stdio::piped()) .stderr(Stdio::piped())
.arg("--standard-json") .arg("--standard-json");
.spawn()?;
if let Some(ref base_path) = input.base_path {
command.arg("--base-path").arg(base_path);
}
if !input.allow_paths.is_empty() {
command.arg("--allow-paths").arg(
input
.allow_paths
.iter()
.map(|path| path.display().to_string())
.collect::<Vec<_>>()
.join(","),
);
}
let mut child = command.spawn()?;
let stdin = child.stdin.as_mut().expect("should be piped"); let stdin = child.stdin.as_mut().expect("should be piped");
serde_json::to_writer(stdin, &input.input)?; serde_json::to_writer(stdin, &input.input)?;
@@ -42,9 +58,26 @@ impl SolidityCompiler for Solc {
}); });
} }
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)
)
})?;
// 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}")
}
}
Ok(CompilerOutput { Ok(CompilerOutput {
input, input,
output: serde_json::from_slice(&output.stdout)?, output: parsed,
error: None, error: None,
}) })
} }
+87 -11
View File
@@ -69,14 +69,13 @@ where
anyhow::bail!("unsupported solc version: {:?}", &mode.solc_version); anyhow::bail!("unsupported solc version: {:?}", &mode.solc_version);
}; };
let mut compiler = Compiler::<T::Compiler>::new() let compiler = Compiler::<T::Compiler>::new()
.base_path(metadata.directory()?.display().to_string()) .allow_path(metadata.directory()?)
.solc_optimizer(mode.solc_optimize()); .solc_optimizer(mode.solc_optimize());
for (file, _contract) in metadata.contract_sources()?.values() { let compiler = FilesWithExtensionIterator::new(metadata.directory()?)
tracing::debug!("contract source {}", file.display()); .with_allowed_extension("sol")
compiler = compiler.with_source(file)?; .try_fold(compiler, |compiler, path| compiler.with_source(&path))?;
}
let mut task = CompilationTask { let mut task = CompilationTask {
json_input: compiler.input(), json_input: compiler.input(),
@@ -180,12 +179,15 @@ where
} }
pub fn deploy_contracts(&mut self, input: &Input, node: &T::Blockchain) -> anyhow::Result<()> { pub fn deploy_contracts(&mut self, input: &Input, node: &T::Blockchain) -> anyhow::Result<()> {
tracing::debug!( let tracing_span = tracing::debug_span!(
"Deploying contracts {}, having address {} on node: {}", "Deploying contracts",
&input.instance, ?input,
&input.caller, node = std::any::type_name::<T>()
std::any::type_name::<T>()
); );
let _guard = tracing_span.enter();
tracing::debug!(number_of_contracts_to_deploy = self.contracts.len());
for output in self.contracts.values() { for output in self.contracts.values() {
let Some(contract_map) = &output.contracts else { let Some(contract_map) = &output.contracts else {
tracing::debug!( tracing::debug!(
@@ -476,3 +478,77 @@ where
Ok(()) Ok(())
} }
} }
/// An iterator that finds files of a certain extension in the provided directory. You can think of
/// this a glob pattern similar to: `${path}/**/*.md`
struct FilesWithExtensionIterator {
/// The set of allowed extensions that that match the requirement and that should be returned
/// when found.
allowed_extensions: std::collections::HashSet<std::borrow::Cow<'static, str>>,
/// The set of directories to visit next. This iterator does BFS and so these directories will
/// only be visited if we can't find any files in our state.
directories_to_search: Vec<std::path::PathBuf>,
/// The set of files matching the allowed extensions that were found. If there are entries in
/// this vector then they will be returned when the [`Iterator::next`] method is called. If not
/// then we visit one of the next directories to visit.
///
/// [`Iterator`]: std::iter::Iterator
files_matching_allowed_extensions: Vec<std::path::PathBuf>,
}
impl FilesWithExtensionIterator {
fn new(root_directory: std::path::PathBuf) -> Self {
Self {
allowed_extensions: Default::default(),
directories_to_search: vec![root_directory],
files_matching_allowed_extensions: Default::default(),
}
}
fn with_allowed_extension(
mut self,
allowed_extension: impl Into<std::borrow::Cow<'static, str>>,
) -> Self {
self.allowed_extensions.insert(allowed_extension.into());
self
}
}
impl Iterator for FilesWithExtensionIterator {
type Item = std::path::PathBuf;
fn next(&mut self) -> Option<Self::Item> {
if let Some(file_path) = self.files_matching_allowed_extensions.pop() {
return Some(file_path);
};
let directory_to_search = self.directories_to_search.pop()?;
// Read all of the entries in the directory. If we failed to read this dir's entires then we
// elect to just ignore it and look in the next directory, we do that by calling the next
// method again on the iterator, which is an intentional decision that we made here instead
// of panicking.
let Ok(dir_entries) = std::fs::read_dir(directory_to_search) else {
return self.next();
};
for entry in dir_entries.flatten() {
let entry_path = entry.path();
if entry_path.is_dir() {
self.directories_to_search.push(entry_path)
} else if entry_path.is_file()
&& entry_path.extension().is_some_and(|ext| {
self.allowed_extensions
.iter()
.any(|allowed| ext.eq_ignore_ascii_case(allowed.as_ref()))
})
{
self.files_matching_allowed_extensions.push(entry_path)
}
}
self.next()
}
}
+8 -3
View File
@@ -123,9 +123,14 @@ impl Input {
return Ok(Bytes::default()); // fallback or deployer — no input return Ok(Bytes::default()); // fallback or deployer — no input
}; };
let abi = deployed_abis let Some(abi) = deployed_abis.get(&self.instance) else {
.get(&self.instance) tracing::error!(
.ok_or_else(|| anyhow::anyhow!("ABI for instance '{}' not found", &self.instance))?; contract_name = self.instance,
available_abis = ?deployed_abis.keys().collect::<Vec<_>>(),
"Attempted to lookup ABI of contract but it wasn't found"
);
anyhow::bail!("ABI for instance '{}' not found", &self.instance);
};
tracing::trace!("ABI found for instance: {}", &self.instance); tracing::trace!("ABI found for instance: {}", &self.instance);