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.
|
/// Supports sync and async functions.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
|
#[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 {
|
impl Default for Solc {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
@ -74,7 +79,25 @@ impl Default for Solc {
|
||||||
impl Solc {
|
impl Solc {
|
||||||
/// A new instance which points to `solc`
|
/// A new instance which points to `solc`
|
||||||
pub fn new(path: impl Into<PathBuf>) -> Self {
|
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
|
/// Returns the directory in which [svm](https://github.com/roynalnaruto/svm-rs) stores all versions
|
||||||
|
@ -112,7 +135,7 @@ impl Solc {
|
||||||
Ok(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`]
|
/// the provided [`VersionReq`]
|
||||||
pub fn find_matching_installation(
|
pub fn find_matching_installation(
|
||||||
versions: &[Version],
|
versions: &[Version],
|
||||||
|
@ -233,7 +256,10 @@ impl Solc {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compile_output<T: Serialize>(&self, input: &T) -> Result<Vec<u8>> {
|
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")
|
.arg("--standard-json")
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
|
@ -248,7 +274,7 @@ impl Solc {
|
||||||
/// Returns the version from the configured `solc`
|
/// Returns the version from the configured `solc`
|
||||||
pub fn version(&self) -> Result<Version> {
|
pub fn version(&self) -> Result<Version> {
|
||||||
version_from_output(
|
version_from_output(
|
||||||
Command::new(&self.0)
|
Command::new(&self.solc)
|
||||||
.arg("--version")
|
.arg("--version")
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stderr(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>> {
|
pub async fn async_compile_output<T: Serialize>(&self, input: &T) -> Result<Vec<u8>> {
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
let content = serde_json::to_vec(input)?;
|
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")
|
.arg("--standard-json")
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
|
@ -302,7 +328,7 @@ impl Solc {
|
||||||
|
|
||||||
pub async fn async_version(&self) -> Result<Version> {
|
pub async fn async_version(&self) -> Result<Version> {
|
||||||
version_from_output(
|
version_from_output(
|
||||||
tokio::process::Command::new(&self.0)
|
tokio::process::Command::new(&self.solc)
|
||||||
.arg("--version")
|
.arg("--version")
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
|
@ -338,13 +364,13 @@ fn version_from_output(output: Output) -> Result<Version> {
|
||||||
|
|
||||||
impl AsRef<Path> for Solc {
|
impl AsRef<Path> for Solc {
|
||||||
fn as_ref(&self) -> &Path {
|
fn as_ref(&self) -> &Path {
|
||||||
&self.0
|
&self.solc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Into<PathBuf>> From<T> for Solc {
|
impl<T: Into<PathBuf>> From<T> for Solc {
|
||||||
fn from(solc: T) -> Self {
|
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 res = Solc::find_svm_installed_version(&version.to_string()).unwrap().unwrap();
|
||||||
let expected = svm::SVM_HOME.join(ver).join(format!("solc-{}", ver));
|
let expected = svm::SVM_HOME.join(ver).join(format!("solc-{}", ver));
|
||||||
assert_eq!(res.0, expected);
|
assert_eq!(res.solc, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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::*;
|
pub use compile::*;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
pub use config::{ArtifactOutput, ProjectPathsConfig, SolcConfig};
|
pub use config::{AllowedLibPaths, ArtifactOutput, ProjectPathsConfig, SolcConfig};
|
||||||
|
|
||||||
use crate::{artifacts::Source, cache::SolFilesCache};
|
use crate::{artifacts::Source, cache::SolFilesCache};
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ use crate::artifacts::Sources;
|
||||||
use error::Result;
|
use error::Result;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, HashMap},
|
collections::{BTreeMap, HashMap},
|
||||||
|
convert::TryInto,
|
||||||
fmt, fs, io,
|
fmt, fs, io,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
@ -40,6 +41,8 @@ pub struct Project {
|
||||||
pub artifacts: ArtifactOutput,
|
pub artifacts: ArtifactOutput,
|
||||||
/// Errors/Warnings which match these error codes are not going to be logged
|
/// Errors/Warnings which match these error codes are not going to be logged
|
||||||
pub ignored_error_codes: Vec<u64>,
|
pub ignored_error_codes: Vec<u64>,
|
||||||
|
/// The paths which will be allowed for library inclusion
|
||||||
|
pub allowed_lib_paths: AllowedLibPaths,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Project {
|
impl Project {
|
||||||
|
@ -116,8 +119,12 @@ impl Project {
|
||||||
let version = Solc::detect_version(&source)?;
|
let version = Solc::detect_version(&source)?;
|
||||||
// gets the solc binary for that version, it is expected tha this will succeed
|
// gets the solc binary for that version, it is expected tha this will succeed
|
||||||
// AND find the solc since it was installed right above
|
// 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");
|
.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);
|
let entry = sources_by_version.entry(solc).or_insert_with(BTreeMap::new);
|
||||||
entry.insert(path, source);
|
entry.insert(path, source);
|
||||||
}
|
}
|
||||||
|
@ -132,7 +139,6 @@ impl Project {
|
||||||
res.contracts.extend(compiled.contracts);
|
res.contracts.extend(compiled.contracts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(if res.contracts.is_empty() {
|
Ok(if res.contracts.is_empty() {
|
||||||
ProjectCompileOutput::Unchanged
|
ProjectCompileOutput::Unchanged
|
||||||
} else {
|
} else {
|
||||||
|
@ -214,6 +220,8 @@ pub struct ProjectBuilder {
|
||||||
artifacts: Option<ArtifactOutput>,
|
artifacts: Option<ArtifactOutput>,
|
||||||
/// Which error codes to ignore
|
/// Which error codes to ignore
|
||||||
pub ignored_error_codes: Vec<u64>,
|
pub ignored_error_codes: Vec<u64>,
|
||||||
|
/// All allowed paths
|
||||||
|
pub allowed_paths: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectBuilder {
|
impl ProjectBuilder {
|
||||||
|
@ -248,8 +256,34 @@ impl ProjectBuilder {
|
||||||
self
|
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> {
|
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 = solc.unwrap_or_default();
|
||||||
let solc_config = solc_config.map(Ok).unwrap_or_else(|| {
|
let solc_config = solc_config.map(Ok).unwrap_or_else(|| {
|
||||||
|
@ -257,13 +291,21 @@ impl ProjectBuilder {
|
||||||
SolcConfig::builder().version(version.to_string()).build()
|
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 {
|
Ok(Project {
|
||||||
paths: paths.map(Ok).unwrap_or_else(ProjectPathsConfig::current_hardhat)?,
|
paths,
|
||||||
solc,
|
solc,
|
||||||
solc_config,
|
solc_config,
|
||||||
cached,
|
cached,
|
||||||
artifacts: artifacts.unwrap_or_default(),
|
artifacts: artifacts.unwrap_or_default(),
|
||||||
ignored_error_codes,
|
ignored_error_codes,
|
||||||
|
allowed_lib_paths: allowed_paths.try_into()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -277,6 +319,7 @@ impl Default for ProjectBuilder {
|
||||||
cached: true,
|
cached: true,
|
||||||
artifacts: None,
|
artifacts: None,
|
||||||
ignored_error_codes: Vec::new(),
|
ignored_error_codes: Vec::new(),
|
||||||
|
allowed_paths: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -329,4 +372,35 @@ mod tests {
|
||||||
// Contracts A to F
|
// Contracts A to F
|
||||||
assert_eq!(contracts.keys().count(), 5);
|
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