feat(solc): allow providing --allow-args (#553)
* feat(solc): allow providing additional arguments * feat(solc): allow providing --allow-args * feat: more allow paths integration (#565) * feat: more allow paths integration * Update ethers-solc/src/compile.rs Co-authored-by: Georgios Konstantopoulos <me@gakonst.com> * chore: only enable macros if feature is enabled * test: add tests for multiple libs * chore: fix solc test * chore: only pas --allow-paths if there are any paths needed Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
parent
dbea856e8c
commit
bfdf70cbea
|
@ -63,7 +63,12 @@ pub static RELEASES: Lazy<Vec<Version>> = Lazy::new(|| {
|
|||
///
|
||||
/// Supports sync and async functions.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Solc(pub PathBuf);
|
||||
pub struct Solc {
|
||||
/// Path to the `solc` executable
|
||||
pub solc: PathBuf,
|
||||
/// Additional arguments passed to the `solc` exectuable
|
||||
pub args: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for Solc {
|
||||
fn default() -> Self {
|
||||
|
@ -74,7 +79,25 @@ impl Default for Solc {
|
|||
impl Solc {
|
||||
/// A new instance which points to `solc`
|
||||
pub fn new(path: impl Into<PathBuf>) -> Self {
|
||||
Solc(path.into())
|
||||
Solc { solc: path.into(), args: Vec::new() }
|
||||
}
|
||||
|
||||
/// Adds an argument to pass to the `solc` command.
|
||||
pub fn arg<T: Into<String>>(mut self, arg: T) -> Self {
|
||||
self.args.push(arg.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds multiple arguments to pass to the `solc`.
|
||||
pub fn args<I, S>(mut self, args: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: Into<String>,
|
||||
{
|
||||
for arg in args {
|
||||
self = self.arg(arg);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the directory in which [svm](https://github.com/roynalnaruto/svm-rs) stores all versions
|
||||
|
@ -112,7 +135,7 @@ impl Solc {
|
|||
Ok(solc)
|
||||
}
|
||||
|
||||
/// Assuming the `versions` array is sorted, it returns the latest element which satisfies
|
||||
/// Assuming the `versions` array is sorted, it returns the first element which satisfies
|
||||
/// the provided [`VersionReq`]
|
||||
pub fn find_matching_installation(
|
||||
versions: &[Version],
|
||||
|
@ -233,7 +256,10 @@ impl Solc {
|
|||
}
|
||||
|
||||
pub fn compile_output<T: Serialize>(&self, input: &T) -> Result<Vec<u8>> {
|
||||
let mut child = Command::new(&self.0)
|
||||
let mut cmd = Command::new(&self.solc);
|
||||
|
||||
let mut child = cmd
|
||||
.args(&self.args)
|
||||
.arg("--standard-json")
|
||||
.stdin(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
|
@ -248,7 +274,7 @@ impl Solc {
|
|||
/// Returns the version from the configured `solc`
|
||||
pub fn version(&self) -> Result<Version> {
|
||||
version_from_output(
|
||||
Command::new(&self.0)
|
||||
Command::new(&self.solc)
|
||||
.arg("--version")
|
||||
.stdin(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
|
@ -288,7 +314,7 @@ impl Solc {
|
|||
pub async fn async_compile_output<T: Serialize>(&self, input: &T) -> Result<Vec<u8>> {
|
||||
use tokio::io::AsyncWriteExt;
|
||||
let content = serde_json::to_vec(input)?;
|
||||
let mut child = tokio::process::Command::new(&self.0)
|
||||
let mut child = tokio::process::Command::new(&self.solc)
|
||||
.arg("--standard-json")
|
||||
.stdin(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
|
@ -302,7 +328,7 @@ impl Solc {
|
|||
|
||||
pub async fn async_version(&self) -> Result<Version> {
|
||||
version_from_output(
|
||||
tokio::process::Command::new(&self.0)
|
||||
tokio::process::Command::new(&self.solc)
|
||||
.arg("--version")
|
||||
.stdin(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
|
@ -338,13 +364,13 @@ fn version_from_output(output: Output) -> Result<Version> {
|
|||
|
||||
impl AsRef<Path> for Solc {
|
||||
fn as_ref(&self) -> &Path {
|
||||
&self.0
|
||||
&self.solc
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<PathBuf>> From<T> for Solc {
|
||||
fn from(solc: T) -> Self {
|
||||
Solc(solc.into())
|
||||
Solc::new(solc.into())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -451,7 +477,7 @@ mod tests {
|
|||
}
|
||||
let res = Solc::find_svm_installed_version(&version.to_string()).unwrap().unwrap();
|
||||
let expected = svm::SVM_HOME.join(ver).join(format!("solc-{}", ver));
|
||||
assert_eq!(res.0, expected);
|
||||
assert_eq!(res.solc, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -272,3 +272,46 @@ impl fmt::Debug for ArtifactOutput {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
/// Helper struct for serializing `--allow-paths` arguments to Solc
|
||||
///
|
||||
/// From the [Solc docs](https://docs.soliditylang.org/en/v0.8.9/using-the-compiler.html#base-path-and-import-remapping):
|
||||
/// For security reasons the compiler has restrictions on what directories it can access.
|
||||
/// Directories of source files specified on the command line and target paths of
|
||||
/// remappings are automatically allowed to be accessed by the file reader,
|
||||
/// but everything else is rejected by default. Additional paths (and their subdirectories)
|
||||
/// can be allowed via the --allow-paths /sample/path,/another/sample/path switch.
|
||||
/// Everything inside the path specified via --base-path is always allowed.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct AllowedLibPaths(pub(crate) Vec<PathBuf>);
|
||||
|
||||
impl fmt::Display for AllowedLibPaths {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let lib_paths = self
|
||||
.0
|
||||
.iter()
|
||||
.filter(|path| path.exists())
|
||||
.map(|path| format!("{}", path.display()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
write!(f, "{}", lib_paths)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<PathBuf>> TryFrom<Vec<T>> for AllowedLibPaths {
|
||||
type Error = std::io::Error;
|
||||
|
||||
fn try_from(libs: Vec<T>) -> std::result::Result<Self, Self::Error> {
|
||||
let libs = libs
|
||||
.into_iter()
|
||||
.map(|lib| {
|
||||
let path: PathBuf = lib.into();
|
||||
let lib = std::fs::canonicalize(path)?;
|
||||
Ok(lib)
|
||||
})
|
||||
.collect::<std::result::Result<Vec<_>, std::io::Error>>()?;
|
||||
Ok(AllowedLibPaths(libs))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ mod compile;
|
|||
pub use compile::*;
|
||||
|
||||
mod config;
|
||||
pub use config::{ArtifactOutput, ProjectPathsConfig, SolcConfig};
|
||||
pub use config::{AllowedLibPaths, ArtifactOutput, ProjectPathsConfig, SolcConfig};
|
||||
|
||||
use crate::{artifacts::Source, cache::SolFilesCache};
|
||||
|
||||
|
@ -21,6 +21,7 @@ use crate::artifacts::Sources;
|
|||
use error::Result;
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
convert::TryInto,
|
||||
fmt, fs, io,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
@ -40,6 +41,8 @@ pub struct Project {
|
|||
pub artifacts: ArtifactOutput,
|
||||
/// Errors/Warnings which match these error codes are not going to be logged
|
||||
pub ignored_error_codes: Vec<u64>,
|
||||
/// The paths which will be allowed for library inclusion
|
||||
pub allowed_lib_paths: AllowedLibPaths,
|
||||
}
|
||||
|
||||
impl Project {
|
||||
|
@ -116,8 +119,12 @@ impl Project {
|
|||
let version = Solc::detect_version(&source)?;
|
||||
// gets the solc binary for that version, it is expected tha this will succeed
|
||||
// AND find the solc since it was installed right above
|
||||
let solc = Solc::find_svm_installed_version(version.to_string())?
|
||||
let mut solc = Solc::find_svm_installed_version(version.to_string())?
|
||||
.expect("solc should have been installed");
|
||||
|
||||
if !self.allowed_lib_paths.0.is_empty() {
|
||||
solc = solc.arg("--allow-paths").arg(self.allowed_lib_paths.to_string());
|
||||
}
|
||||
let entry = sources_by_version.entry(solc).or_insert_with(BTreeMap::new);
|
||||
entry.insert(path, source);
|
||||
}
|
||||
|
@ -132,7 +139,6 @@ impl Project {
|
|||
res.contracts.extend(compiled.contracts);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(if res.contracts.is_empty() {
|
||||
ProjectCompileOutput::Unchanged
|
||||
} else {
|
||||
|
@ -214,6 +220,8 @@ pub struct ProjectBuilder {
|
|||
artifacts: Option<ArtifactOutput>,
|
||||
/// Which error codes to ignore
|
||||
pub ignored_error_codes: Vec<u64>,
|
||||
/// All allowed paths
|
||||
pub allowed_paths: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl ProjectBuilder {
|
||||
|
@ -248,8 +256,34 @@ impl ProjectBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
/// Adds an allowed-path to the solc executable
|
||||
pub fn allowed_path<T: Into<PathBuf>>(mut self, path: T) -> Self {
|
||||
self.allowed_paths.push(path.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds multiple allowed-path to the solc executable
|
||||
pub fn allowed_paths<I, S>(mut self, args: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: Into<PathBuf>,
|
||||
{
|
||||
for arg in args {
|
||||
self = self.allowed_path(arg);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<Project> {
|
||||
let Self { paths, solc, solc_config, cached, artifacts, ignored_error_codes } = self;
|
||||
let Self {
|
||||
paths,
|
||||
solc,
|
||||
solc_config,
|
||||
cached,
|
||||
artifacts,
|
||||
ignored_error_codes,
|
||||
mut allowed_paths,
|
||||
} = self;
|
||||
|
||||
let solc = solc.unwrap_or_default();
|
||||
let solc_config = solc_config.map(Ok).unwrap_or_else(|| {
|
||||
|
@ -257,13 +291,21 @@ impl ProjectBuilder {
|
|||
SolcConfig::builder().version(version.to_string()).build()
|
||||
})?;
|
||||
|
||||
let paths = paths.map(Ok).unwrap_or_else(ProjectPathsConfig::current_hardhat)?;
|
||||
|
||||
if allowed_paths.is_empty() {
|
||||
// allow every contract under root by default
|
||||
allowed_paths.push(paths.root.clone())
|
||||
}
|
||||
|
||||
Ok(Project {
|
||||
paths: paths.map(Ok).unwrap_or_else(ProjectPathsConfig::current_hardhat)?,
|
||||
paths,
|
||||
solc,
|
||||
solc_config,
|
||||
cached,
|
||||
artifacts: artifacts.unwrap_or_default(),
|
||||
ignored_error_codes,
|
||||
allowed_lib_paths: allowed_paths.try_into()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -277,6 +319,7 @@ impl Default for ProjectBuilder {
|
|||
cached: true,
|
||||
artifacts: None,
|
||||
ignored_error_codes: Vec::new(),
|
||||
allowed_paths: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -329,4 +372,35 @@ mod tests {
|
|||
// Contracts A to F
|
||||
assert_eq!(contracts.keys().count(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(feature = "svm", feature = "async"))]
|
||||
fn test_build_many_libs() {
|
||||
use super::*;
|
||||
|
||||
let root = std::fs::canonicalize("./test-data/test-contract-libs").unwrap();
|
||||
|
||||
let paths = ProjectPathsConfig::builder()
|
||||
.root(&root)
|
||||
.sources(root.join("src"))
|
||||
.lib(root.join("lib1"))
|
||||
.lib(root.join("lib2"))
|
||||
.build()
|
||||
.unwrap();
|
||||
let project = Project::builder()
|
||||
.paths(paths)
|
||||
.ephemeral()
|
||||
.artifacts(ArtifactOutput::Nothing)
|
||||
.build()
|
||||
.unwrap();
|
||||
let compiled = project.compile().unwrap();
|
||||
let contracts = match compiled {
|
||||
ProjectCompileOutput::Compiled((out, _)) => {
|
||||
assert!(!out.has_error());
|
||||
out.contracts
|
||||
}
|
||||
_ => panic!("must compile"),
|
||||
};
|
||||
assert_eq!(contracts.keys().count(), 3);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
pragma solidity ^0.8.6;
|
||||
|
||||
contract Bar {}
|
|
@ -0,0 +1,3 @@
|
|||
pragma solidity ^0.8.6;
|
||||
|
||||
contract Baz {}
|
|
@ -0,0 +1,6 @@
|
|||
pragma solidity 0.8.6;
|
||||
|
||||
import "../lib1/Bar.sol";
|
||||
import "../lib2/Baz.sol";
|
||||
|
||||
contract Foo is Bar, Baz {}
|
Loading…
Reference in New Issue