diff --git a/ethers-solc/src/compile/mod.rs b/ethers-solc/src/compile/mod.rs
index dde89f04..46ebed6c 100644
--- a/ethers-solc/src/compile/mod.rs
+++ b/ethers-solc/src/compile/mod.rs
@@ -40,6 +40,14 @@ pub const BERLIN_SOLC: Version = Version::new(0, 8, 5);
///
pub const LONDON_SOLC: Version = Version::new(0, 8, 7);
+// `--base-path` was introduced in 0.6.9
+pub static SUPPORTS_BASE_PATH: once_cell::sync::Lazy =
+ once_cell::sync::Lazy::new(|| VersionReq::parse(">=0.6.9").unwrap());
+
+// `--include-path` was introduced in 0.8.8
+pub static SUPPORTS_INCLUDE_PATH: once_cell::sync::Lazy =
+ once_cell::sync::Lazy::new(|| VersionReq::parse(">=0.8.8").unwrap());
+
#[cfg(any(test, feature = "tests"))]
use std::sync::Mutex;
@@ -527,6 +535,7 @@ impl Solc {
let mut cmd = Command::new(&self.solc);
if let Some(ref base_path) = self.base_path {
cmd.current_dir(base_path);
+ cmd.arg("--base-path").arg(base_path);
}
let mut child = cmd
.args(&self.args)
diff --git a/ethers-solc/src/compile/project.rs b/ethers-solc/src/compile/project.rs
index 5275efbe..f0fdce64 100644
--- a/ethers-solc/src/compile/project.rs
+++ b/ethers-solc/src/compile/project.rs
@@ -156,8 +156,7 @@ impl<'a, T: ArtifactOutput> ProjectCompiler<'a, T> {
let graph = Graph::resolve_sources(&project.paths, sources)?;
let (versions, edges) = graph.into_sources_by_version(project.offline)?;
- let base_path = project.root();
- let sources_by_version = versions.get(&project.allowed_lib_paths, base_path)?;
+ let sources_by_version = versions.get(project)?;
let sources = if project.solc_jobs > 1 && sources_by_version.len() > 1 {
// if there are multiple different versions, and we can use multiple jobs we can compile
@@ -178,6 +177,14 @@ impl<'a, T: ArtifactOutput> ProjectCompiler<'a, T> {
) -> Result {
let version = solc.version()?;
let (sources, edges) = Graph::resolve_sources(&project.paths, sources)?.into_sources();
+
+ // make sure `solc` has all required arguments
+ let solc = project.configure_solc_with_version(
+ solc,
+ Some(version.clone()),
+ edges.include_paths().clone(),
+ );
+
let sources_by_version = BTreeMap::from([(solc, (version, sources))]);
let sources = CompilerSources::Sequential(sources_by_version);
diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs
index 30714b76..41a52ad3 100644
--- a/ethers-solc/src/config.rs
+++ b/ethers-solc/src/config.rs
@@ -1,19 +1,17 @@
use crate::{
- artifacts::Settings,
+ artifacts::{output_selection::ContractOutputSelection, Settings},
cache::SOLIDITY_FILES_CACHE_FILENAME,
error::{Result, SolcError, SolcIoError},
remappings::Remapping,
resolver::{Graph, SolImportAlias},
utils, Source, Sources,
};
-
-use crate::artifacts::output_selection::ContractOutputSelection;
-
use serde::{Deserialize, Serialize};
use std::{
collections::{BTreeSet, HashSet},
fmt::{self, Formatter},
fs,
+ ops::{Deref, DerefMut},
path::{Component, Path, PathBuf},
};
@@ -86,6 +84,15 @@ impl ProjectPathsConfig {
paths
}
+ /// Returns all `--include-path` paths that should be used for this project
+ ///
+ /// See [IncludePaths]
+ pub fn include_paths(&self) -> Vec {
+ // Note: root must not be included, since it will be used as base-path, which would be a
+ // conflict
+ vec![self.sources.clone(), self.tests.clone(), self.scripts.clone()]
+ }
+
/// Creates all configured dirs and files
pub fn create_all(&self) -> std::result::Result<(), SolcIoError> {
if let Some(parent) = self.cache.parent() {
@@ -214,11 +221,20 @@ impl ProjectPathsConfig {
/// Attempts to resolve an `import` from the given working directory.
///
/// The `cwd` path is the parent dir of the file that includes the `import`
- pub fn resolve_import(&self, cwd: &Path, import: &Path) -> Result {
+ ///
+ /// This will also populate the `include_paths` with any nested library root paths that should
+ /// be provided to solc via `--include-path` because it uses absolute imports.
+ pub fn resolve_import_and_include_paths(
+ &self,
+ cwd: &Path,
+ import: &Path,
+ include_paths: &mut IncludePaths,
+ ) -> Result {
let component = import
.components()
.next()
.ok_or_else(|| SolcError::msg(format!("Empty import path {}", import.display())))?;
+
if component == Component::CurDir || component == Component::ParentDir {
// if the import is relative we assume it's already part of the processed input
// file set
@@ -227,7 +243,32 @@ impl ProjectPathsConfig {
})
} else {
// resolve library file
- self.resolve_library_import(import.as_ref()).ok_or_else(|| {
+ let resolved = self.resolve_library_import(import.as_ref());
+
+ if resolved.is_none() {
+ // absolute paths in solidity are a thing for example `import
+ // "src/interfaces/IConfig.sol"` which could either point to `cwd +
+ // src/interfaces/IConfig.sol`, or make use of a remapping (`src/=....`)
+ if let Some(lib) = self.find_library_ancestor(cwd) {
+ if let Some((include_path, import)) =
+ utils::resolve_absolute_library(lib, cwd, import)
+ {
+ // track the path for this absolute import inside a nested library
+ include_paths.insert(include_path);
+ return Ok(import)
+ }
+ }
+ // also try to resolve absolute imports from the project paths
+ for path in [&self.root, &self.sources, &self.tests, &self.scripts] {
+ if cwd.starts_with(path) {
+ if let Ok(import) = utils::canonicalize(path.join(import)) {
+ return Ok(import)
+ }
+ }
+ }
+ }
+
+ resolved.ok_or_else(|| {
SolcError::msg(format!(
"failed to resolve library import \"{:?}\"",
import.display()
@@ -236,6 +277,13 @@ impl ProjectPathsConfig {
}
}
+ /// Attempts to resolve an `import` from the given working directory.
+ ///
+ /// The `cwd` path is the parent dir of the file that includes the `import`
+ pub fn resolve_import(&self, cwd: &Path, import: &Path) -> Result {
+ self.resolve_import_and_include_paths(cwd, import, &mut Default::default())
+ }
+
/// Attempts to find the path to the real solidity file that's imported via the given `import`
/// path by applying the configured remappings and checking the library dirs
///
@@ -751,6 +799,49 @@ impl SolcConfigBuilder {
}
}
+/// Container for all `--include-path` arguments for Solc, se also [Solc docs](https://docs.soliditylang.org/en/v0.8.9/using-the-compiler.html#base-path-and-import-remapping
+///
+/// The `--include--path` flag:
+/// > Makes an additional source directory available to the default import callback. Use this option
+/// > if you want to import contracts whose location is not fixed in relation to your main source
+/// > tree, e.g. third-party libraries installed using a package manager. Can be used multiple
+/// > times. Can only be used if base path has a non-empty value.
+///
+/// In contrast to `--allow-paths` [`AllowedLibPaths`], which takes multiple arguments,
+/// `--include-path` only takes a single path argument.
+#[derive(Clone, Debug, Default)]
+pub struct IncludePaths(pub(crate) BTreeSet);
+
+// === impl IncludePaths ===
+
+impl IncludePaths {
+ /// Returns the [Command](std::process::Command) arguments for this type
+ ///
+ /// For each entry in the set, it will return `--include-path` + ``
+ pub fn args(&self) -> impl Iterator- + '_ {
+ self.paths().flat_map(|path| ["--include-path".to_string(), format!("{}", path.display())])
+ }
+
+ /// Returns all paths that exist
+ pub fn paths(&self) -> impl Iterator
- + '_ {
+ self.0.iter().filter(|path| path.exists())
+ }
+}
+
+impl Deref for IncludePaths {
+ type Target = BTreeSet;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl DerefMut for IncludePaths {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
/// 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):
@@ -761,23 +852,46 @@ impl SolcConfigBuilder {
/// 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);
+pub struct AllowedLibPaths(pub(crate) BTreeSet);
+
+// === impl AllowedLibPaths ===
impl AllowedLibPaths {
- pub fn is_empty(&self) -> bool {
- self.0.is_empty()
+ /// Returns the [Command](std::process::Command) arguments for this type
+ ///
+ /// `--allow-paths` takes a single value: all comma separated paths
+ pub fn args(&self) -> Option<[String; 2]> {
+ let args = self.to_string();
+ if args.is_empty() {
+ return None
+ }
+ Some(["--allow-paths".to_string(), args])
+ }
+
+ /// Returns all paths that exist
+ pub fn paths(&self) -> impl Iterator
- + '_ {
+ self.0.iter().filter(|path| path.exists())
+ }
+}
+
+impl Deref for AllowedLibPaths {
+ type Target = BTreeSet;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl DerefMut for AllowedLibPaths {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
}
}
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::>()
- .join(",");
+ let lib_paths =
+ self.paths().map(|path| format!("{}", path.display())).collect::>().join(",");
write!(f, "{}", lib_paths)
}
}
diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs
index 0e39c8a7..424c26c7 100644
--- a/ethers-solc/src/lib.rs
+++ b/ethers-solc/src/lib.rs
@@ -38,6 +38,7 @@ pub use filter::{FileFilter, TestFileFilter};
use crate::{
artifacts::Sources,
cache::SolFilesCache,
+ config::IncludePaths,
error::{SolcError, SolcIoError},
sources::{VersionedSourceFile, VersionedSourceFiles},
};
@@ -73,7 +74,9 @@ pub struct Project {
/// Errors/Warnings which match these error codes are not going to be logged
pub ignored_error_codes: Vec,
/// The paths which will be allowed for library inclusion
- pub allowed_lib_paths: AllowedLibPaths,
+ pub allowed_paths: AllowedLibPaths,
+ /// The paths which will be used with solc's `--include-path` attribute
+ pub include_paths: IncludePaths,
/// Maximum number of `solc` processes to run simultaneously.
solc_jobs: usize,
/// Offline mode, if set, network access (download solc) is disallowed
@@ -151,16 +154,48 @@ impl Project {
SolFilesCache::read_joined(&self.paths)
}
+ /// Applies the configured arguments to the given `Solc`
+ ///
+ /// See [Self::configure_solc_with_version()]
+ pub(crate) fn configure_solc(&self, solc: Solc) -> Solc {
+ let version = solc.version().ok();
+ self.configure_solc_with_version(solc, version, Default::default())
+ }
+
/// Applies the configured arguments to the given `Solc`
///
/// This will set the `--allow-paths` to the paths configured for the `Project`, if any.
- fn configure_solc(&self, mut solc: Solc) -> Solc {
- if !self.allowed_lib_paths.0.is_empty() &&
- !solc.args.iter().any(|arg| arg == "--allow-paths")
- {
- solc = solc.arg("--allow-paths").arg(self.allowed_lib_paths.to_string());
+ ///
+ /// If a version is provided and it is applicable it will also set `--base-path` and
+ /// `--include-path` This will set the `--allow-paths` to the paths configured for the
+ /// `Project`, if any.
+ /// This also accepts additional `include_paths`
+ pub(crate) fn configure_solc_with_version(
+ &self,
+ mut solc: Solc,
+ version: Option,
+ mut include_paths: IncludePaths,
+ ) -> Solc {
+ if !solc.args.iter().any(|arg| arg == "--allow-paths") {
+ if let Some([allow, libs]) = self.allowed_paths.args() {
+ solc = solc.arg(allow).arg(libs);
+ }
}
- solc.with_base_path(self.root())
+ if let Some(version) = version {
+ if SUPPORTS_BASE_PATH.matches(&version) {
+ let base_path = format!("{}", self.root().display());
+ if !base_path.is_empty() {
+ solc = solc.with_base_path(self.root());
+ if SUPPORTS_INCLUDE_PATH.matches(&version) {
+ include_paths.extend(self.include_paths.paths().cloned());
+ solc = solc.args(include_paths.args());
+ }
+ }
+ } else {
+ solc.base_path.take();
+ }
+ }
+ solc
}
/// Sets the maximum number of parallel `solc` processes to run simultaneously.
@@ -232,9 +267,7 @@ impl Project {
return self.svm_compile(sources)
}
- let solc = self.configure_solc(self.solc.clone());
-
- self.compile_with_version(&solc, sources)
+ self.compile_with_version(&self.solc, sources)
}
/// Compiles a set of contracts using `svm` managed solc installs
@@ -360,13 +393,9 @@ impl Project {
.compile()
}
- project::ProjectCompiler::with_sources_and_solc(
- self,
- sources,
- self.configure_solc(self.solc.clone()),
- )?
- .with_sparse_output(filter)
- .compile()
+ project::ProjectCompiler::with_sources_and_solc(self, sources, self.solc.clone())?
+ .with_sparse_output(filter)
+ .compile()
}
/// Compiles the given source files with the exact `Solc` executable
@@ -397,12 +426,7 @@ impl Project {
solc: &Solc,
sources: Sources,
) -> Result> {
- project::ProjectCompiler::with_sources_and_solc(
- self,
- sources,
- self.configure_solc(solc.clone()),
- )?
- .compile()
+ project::ProjectCompiler::with_sources_and_solc(self, sources, solc.clone())?.compile()
}
/// Removes the project's artifacts and cache file
@@ -541,8 +565,10 @@ pub struct ProjectBuilder {
artifacts: T,
/// Which error codes to ignore
pub ignored_error_codes: Vec,
- /// All allowed paths
- pub allowed_paths: Vec,
+ /// All allowed paths for solc's `--allowed-paths`
+ allowed_paths: AllowedLibPaths,
+ /// Paths to use for solc's `--include-path`
+ include_paths: IncludePaths,
solc_jobs: Option,
}
@@ -561,7 +587,8 @@ impl ProjectBuilder {
slash_paths: true,
artifacts,
ignored_error_codes: Vec::new(),
- allowed_paths: vec![],
+ allowed_paths: Default::default(),
+ include_paths: Default::default(),
solc_jobs: None,
}
}
@@ -697,6 +724,7 @@ impl ProjectBuilder {
auto_detect,
ignored_error_codes,
allowed_paths,
+ include_paths,
solc_jobs,
offline,
build_info,
@@ -715,6 +743,7 @@ impl ProjectBuilder {
artifacts,
ignored_error_codes,
allowed_paths,
+ include_paths,
solc_jobs,
build_info,
}
@@ -723,7 +752,7 @@ impl ProjectBuilder {
/// Adds an allowed-path to the solc executable
#[must_use]
pub fn allowed_path>(mut self, path: P) -> Self {
- self.allowed_paths.push(path.into());
+ self.allowed_paths.insert(path.into());
self
}
@@ -740,6 +769,26 @@ impl ProjectBuilder {
self
}
+ /// Adds an `--include-path` to the solc executable
+ #[must_use]
+ pub fn include_path>(mut self, path: P) -> Self {
+ self.include_paths.insert(path.into());
+ self
+ }
+
+ /// Adds multiple include-path to the solc executable
+ #[must_use]
+ pub fn include_paths(mut self, args: I) -> Self
+ where
+ I: IntoIterator
- ,
+ S: Into,
+ {
+ for arg in args {
+ self = self.include_path(arg);
+ }
+ self
+ }
+
pub fn build(self) -> Result> {
let Self {
paths,
@@ -751,6 +800,7 @@ impl ProjectBuilder {
artifacts,
ignored_error_codes,
mut allowed_paths,
+ mut include_paths,
solc_jobs,
offline,
build_info,
@@ -767,10 +817,10 @@ impl ProjectBuilder {
let solc = solc.unwrap_or_default();
let solc_config = solc_config.unwrap_or_else(|| SolcConfig::builder().build());
- if allowed_paths.is_empty() {
- // allow every contract under root by default
- allowed_paths.push(paths.root.clone())
- }
+ // allow every contract under root by default
+ allowed_paths.insert(paths.root.clone());
+ // allow paths where contracts are stored by default
+ include_paths.extend(paths.include_paths());
Ok(Project {
paths,
@@ -782,8 +832,9 @@ impl ProjectBuilder {
auto_detect,
artifacts,
ignored_error_codes,
- allowed_lib_paths: allowed_paths.into(),
- solc_jobs: solc_jobs.unwrap_or_else(::num_cpus::get),
+ allowed_paths,
+ include_paths,
+ solc_jobs: solc_jobs.unwrap_or_else(num_cpus::get),
offline,
slash_paths,
})
diff --git a/ethers-solc/src/resolver/mod.rs b/ethers-solc/src/resolver/mod.rs
index 374f678e..3c68f2d6 100644
--- a/ethers-solc/src/resolver/mod.rs
+++ b/ethers-solc/src/resolver/mod.rs
@@ -46,19 +46,16 @@
//! [version pragma](https://docs.soliditylang.org/en/develop/layout-of-source-files.html#version-pragma),
//! which is defined on a per source file basis.
+use crate::{error::Result, utils, IncludePaths, ProjectPathsConfig, SolcError, Source, Sources};
+use parse::{SolData, SolDataUnit, SolImport};
+use rayon::prelude::*;
+use semver::VersionReq;
use std::{
collections::{HashMap, HashSet, VecDeque},
fmt, io,
path::{Path, PathBuf},
};
-use parse::{SolData, SolDataUnit, SolImport};
-use rayon::prelude::*;
-
-use semver::VersionReq;
-
-use crate::{error::Result, utils, ProjectPathsConfig, SolcError, Source, Sources};
-
mod parse;
mod tree;
@@ -90,6 +87,13 @@ pub struct GraphEdges {
num_input_files: usize,
/// tracks all imports that we failed to resolve for a file
unresolved_imports: HashSet<(PathBuf, PathBuf)>,
+ /// tracks additional include paths resolved by scanning all imports of the graph
+ ///
+ /// Absolute imports, like `import "src/Contract.sol"` are possible, but this does not play
+ /// nice with the standard-json import format, since the VFS won't be able to resolve
+ /// "src/Contract.sol" without help via `--include-path`
+ #[allow(unused)]
+ resolved_solc_include_paths: IncludePaths,
}
impl GraphEdges {
@@ -113,6 +117,11 @@ impl GraphEdges {
self.files().skip(self.num_input_files)
}
+ /// Returns all additional `--include-paths`
+ pub fn include_paths(&self) -> &IncludePaths {
+ &self.resolved_solc_include_paths
+ }
+
/// Returns all imports that we failed to resolve
pub fn unresolved_imports(&self) -> &HashSet<(PathBuf, PathBuf)> {
&self.unresolved_imports
@@ -333,6 +342,10 @@ impl Graph {
let mut nodes = Vec::with_capacity(unresolved.len());
let mut edges = Vec::with_capacity(unresolved.len());
+ // tracks additional paths that should be used with `--include-path`, these are libraries
+ // that use absolute imports like `import "src/Contract.sol"`
+ let mut resolved_solc_include_paths = IncludePaths::default();
+
// keep track of all unique paths that we failed to resolve to not spam the reporter with
// the same path
let mut unresolved_imports = HashSet::new();
@@ -349,7 +362,11 @@ impl Graph {
for import in node.data.imports.iter() {
let import_path = import.data().path();
- match paths.resolve_import(cwd, import_path) {
+ match paths.resolve_import_and_include_paths(
+ cwd,
+ import_path,
+ &mut resolved_solc_include_paths,
+ ) {
Ok(import) => {
add_node(&mut unresolved, &mut index, &mut resolved_imports, import)?;
}
@@ -391,6 +408,7 @@ impl Graph {
.collect(),
data: Default::default(),
unresolved_imports,
+ resolved_solc_include_paths,
};
Ok(Graph { nodes, edges, root: paths.root.clone() })
}
@@ -461,7 +479,14 @@ impl Graph {
}
versioned_sources.insert(version, sources);
}
- Ok((VersionedSources { inner: versioned_sources, offline }, edges))
+ Ok((
+ VersionedSources {
+ inner: versioned_sources,
+ offline,
+ resolved_solc_include_paths: edges.resolved_solc_include_paths.clone(),
+ },
+ edges,
+ ))
}
/// Writes the list of imported files into the given formatter:
@@ -713,6 +738,7 @@ impl<'a> Iterator for NodesIter<'a> {
#[cfg(all(feature = "svm-solc"))]
#[derive(Debug)]
pub struct VersionedSources {
+ resolved_solc_include_paths: IncludePaths,
inner: HashMap,
offline: bool,
}
@@ -724,17 +750,11 @@ impl VersionedSources {
/// This will also configure following solc arguments:
/// - `allowed_paths`
/// - `base_path`
- pub fn get(
+ pub fn get(
self,
- allowed_lib_paths: &crate::AllowedLibPaths,
- base_path: impl AsRef,
+ project: &crate::Project,
) -> Result> {
use crate::Solc;
-
- // `--base-path` was introduced in 0.6.9
- static SUPPORTS_BASE_PATH: once_cell::sync::Lazy =
- once_cell::sync::Lazy::new(|| VersionReq::parse(">=0.6.9").unwrap());
-
// we take the installer lock here to ensure installation checking is done in sync
#[cfg(any(test, feature = "tests"))]
let _lock = crate::compile::take_solc_installer_lock();
@@ -771,16 +791,15 @@ impl VersionedSources {
tracing::trace!("reinstalled solc: \"{}\"", version);
}
}
- let mut solc = solc
- .arg("--allow-paths")
- .arg(allowed_lib_paths.to_string())
- .with_base_path(base_path.as_ref());
+
let version = solc.version()?;
- if SUPPORTS_BASE_PATH.matches(&version) {
- solc = solc.arg("--base-path").arg(format!("{}", base_path.as_ref().display()));
- }
-
+ // this will configure the `Solc` executable and its arguments
+ let solc = project.configure_solc_with_version(
+ solc,
+ Some(version.clone()),
+ self.resolved_solc_include_paths.clone(),
+ );
sources_by_version.insert(solc, (version, sources));
}
Ok(sources_by_version)
diff --git a/ethers-solc/src/utils.rs b/ethers-solc/src/utils.rs
index a14e6d27..89636366 100644
--- a/ethers-solc/src/utils.rs
+++ b/ethers-solc/src/utils.rs
@@ -223,6 +223,37 @@ pub fn resolve_library(libs: &[impl AsRef], source: impl AsRef) -> O
}
}
+/// Tries to find an absolute import like `src/interfaces/IConfig.sol` in `cwd`, moving up the path
+/// until the `root` is reached.
+///
+/// If an existing file under `root` is found, this returns the path up to the `import` path and the
+/// canonicalized `import` path itself:
+///
+/// For example for following layout:
+///
+/// ```text
+/// /mydependency/
+/// ├── src (`cwd`)
+/// │ ├── interfaces
+/// │ │ ├── IConfig.sol
+/// ```
+/// and `import` as `src/interfaces/IConfig.sol` and `cwd` as `src` this will return
+/// (`/mydependency/`, `/mydependency/src/interfaces/IConfig.sol`)
+pub fn resolve_absolute_library(
+ root: &Path,
+ cwd: &Path,
+ import: &Path,
+) -> Option<(PathBuf, PathBuf)> {
+ let mut parent = cwd.parent()?;
+ while parent != root {
+ if let Ok(import) = canonicalize(parent.join(import)) {
+ return Some((parent.to_path_buf(), import))
+ }
+ parent = parent.parent()?;
+ }
+ None
+}
+
/// Reads the list of Solc versions that have been installed in the machine. The version list is
/// sorted in ascending order.
/// Checks for installed solc versions under the given path as
diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs
index 7ce00970..4944e0ee 100644
--- a/ethers-solc/tests/project.rs
+++ b/ethers-solc/tests/project.rs
@@ -1202,7 +1202,7 @@ library MyLib {
#[test]
fn can_recompile_with_changes() {
let mut tmp = TempProject::dapptools().unwrap();
- tmp.project_mut().allowed_lib_paths = vec![tmp.root().join("modules")].into();
+ tmp.project_mut().allowed_paths = vec![tmp.root().join("modules")].into();
let content = r#"
pragma solidity ^0.8.10;
@@ -2211,3 +2211,110 @@ fn can_add_basic_contract_and_library() {
assert!(compiled.find_first("Foo").is_some());
assert!(compiled.find_first("Bar").is_some());
}
+
+//
+#[test]
+fn can_handle_nested_absolute_imports() {
+ let mut project = TempProject::dapptools().unwrap();
+
+ let remapping = project.paths().libraries[0].join("myDepdendency");
+ project
+ .paths_mut()
+ .remappings
+ .push(Remapping::from_str(&format!("myDepdendency/={}/", remapping.display())).unwrap());
+
+ project
+ .add_lib(
+ "myDepdendency/src/interfaces/IConfig.sol",
+ r#"
+ pragma solidity ^0.8.10;
+
+ interface IConfig {}
+ "#,
+ )
+ .unwrap();
+
+ project
+ .add_lib(
+ "myDepdendency/src/Config.sol",
+ r#"
+ pragma solidity ^0.8.10;
+ import "src/interfaces/IConfig.sol";
+
+ contract Config {}
+ "#,
+ )
+ .unwrap();
+
+ project
+ .add_source(
+ "Greeter",
+ r#"
+ pragma solidity ^0.8.10;
+ import "myDepdendency/src/Config.sol";
+
+ contract Greeter {}
+ "#,
+ )
+ .unwrap();
+
+ let compiled = project.compile().unwrap();
+ assert!(!compiled.has_compiler_errors());
+ assert!(compiled.find_first("Greeter").is_some());
+ assert!(compiled.find_first("Config").is_some());
+ assert!(compiled.find_first("IConfig").is_some());
+}
+
+#[test]
+fn can_handle_nested_test_absolute_imports() {
+ let project = TempProject::dapptools().unwrap();
+
+ project
+ .add_source(
+ "Contract.sol",
+ r#"
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity =0.8.13;
+
+library Library {
+ function f(uint256 a, uint256 b) public pure returns (uint256) {
+ return a + b;
+ }
+}
+
+contract Contract {
+ uint256 c;
+
+ constructor() {
+ c = Library.f(1, 2);
+ }
+}
+ "#,
+ )
+ .unwrap();
+
+ project
+ .add_test(
+ "Contract.t.sol",
+ r#"
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity =0.8.13;
+
+import "src/Contract.sol";
+
+contract ContractTest {
+ function setUp() public {
+ }
+
+ function test() public {
+ new Contract();
+ }
+}
+ "#,
+ )
+ .unwrap();
+
+ let compiled = project.compile().unwrap();
+ assert!(!compiled.has_compiler_errors());
+ assert!(compiled.find_first("Contract").is_some());
+}