diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs index 8bce8d9e..c60bd8e3 100644 --- a/ethers-solc/src/artifacts/mod.rs +++ b/ethers-solc/src/artifacts/mod.rs @@ -144,6 +144,25 @@ impl CompilerInput { self.settings.remappings = remappings; self } + + /// Sets the path of the source files to `root` adjoined to the existing path + #[must_use] + pub fn join_path(mut self, root: impl AsRef) -> Self { + let root = root.as_ref(); + self.sources = self.sources.into_iter().map(|(path, s)| (root.join(path), s)).collect(); + self + } + + /// Removes the `base` path from all source files + pub fn strip_prefix(mut self, base: impl AsRef) -> Self { + let base = base.as_ref(); + self.sources = self + .sources + .into_iter() + .map(|(path, s)| (path.strip_prefix(base).map(|p| p.to_path_buf()).unwrap_or(path), s)) + .collect(); + self + } } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs index 506b8c9f..0865ad95 100644 --- a/ethers-solc/src/lib.rs +++ b/ethers-solc/src/lib.rs @@ -428,8 +428,9 @@ impl Project { } /// Returns standard-json-input to compile the target contract - pub fn standard_json_input(&self, target: &Path) -> Result { - tracing::trace!("Building standard-json-input"); + pub fn standard_json_input(&self, target: impl AsRef) -> Result { + let target = target.as_ref(); + tracing::trace!("Building standard-json-input for {:?}", target); let graph = Graph::resolve(&self.paths)?; let target_index = graph.files().get(target).ok_or_else(|| { SolcError::msg(format!("cannot resolve file at {:?}", target.display())) @@ -444,12 +445,23 @@ impl Project { let compiler_inputs = CompilerInput::with_sources( sources.into_iter().map(|(s, p)| (s.clone(), p.clone())).collect(), ); + + // strip the path to the project root from all remappings + let remappings = self + .paths + .remappings + .clone() + .into_iter() + .map(|r| r.into_relative(self.root()).to_relative_remapping()) + .collect::>(); + let compiler_input = compiler_inputs .first() .ok_or_else(|| SolcError::msg("cannot get the compiler input"))? .clone() .settings(self.solc_config.settings.clone()) - .with_remappings(self.paths.remappings.clone()); + .with_remappings(remappings) + .strip_prefix(self.root()); Ok(compiler_input) } diff --git a/ethers-solc/src/project_util/mod.rs b/ethers-solc/src/project_util/mod.rs index a415c96a..afa3298f 100644 --- a/ethers-solc/src/project_util/mod.rs +++ b/ethers-solc/src/project_util/mod.rs @@ -5,6 +5,8 @@ use crate::{ error::{bail, Result, SolcError}, hh::HardhatArtifacts, project_util::mock::{MockProjectGenerator, MockProjectSettings}, + remappings::Remapping, + utils, utils::tempdir, Artifact, ArtifactOutput, Artifacts, ConfigurableArtifacts, ConfigurableContractArtifact, FileFilter, PathStyle, Project, ProjectCompileOutput, ProjectPathsConfig, SolFilesCache, @@ -311,6 +313,11 @@ contract {} {{}} assert!(!compiled.is_unchanged()); self } + + /// Returns a list of all source files in the project's `src` directory + pub fn list_source_files(&self) -> Vec { + utils::source_files(self.project().sources_path()) + } } impl TempProject { @@ -381,6 +388,16 @@ impl TempProject { Ok(Self::create_new(tmp_dir, inner)?) } + /// Creates an initialized dapptools style workspace in a new temporary dir + pub fn dapptools_init() -> Result { + let mut project = Self::dapptools()?; + let orig_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/dapp-sample"); + copy_dir(&orig_root, project.root())?; + project.project_mut().paths.remappings = Remapping::find_many(project.root()); + + Ok(project) + } + /// Create a new temporary project and populate it with mock files /// /// ```no_run diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs index fd3498d7..86939884 100644 --- a/ethers-solc/tests/project.rs +++ b/ethers-solc/tests/project.rs @@ -13,7 +13,7 @@ use ethers_solc::{ project_util::*, remappings::Remapping, ConfigurableArtifacts, ExtraOutputValues, Graph, Project, ProjectCompileOutput, - ProjectPathsConfig, TestFileFilter, + ProjectPathsConfig, Solc, TestFileFilter, }; use pretty_assertions::assert_eq; @@ -955,3 +955,22 @@ fn can_sanitize_bytecode_hash() { assert!(!compiled.has_compiler_errors()); assert!(compiled.find("A").is_some()); } + +#[test] +fn can_compile_std_json_input() { + let tmp = TempProject::dapptools_init().unwrap(); + tmp.assert_no_errors(); + let source = + tmp.list_source_files().into_iter().filter(|p| p.ends_with("Dapp.t.sol")).next().unwrap(); + let input = tmp.project().standard_json_input(source).unwrap(); + + assert!(input.settings.remappings.contains(&"ds-test/=lib/ds-test/src/".parse().unwrap())); + assert!(input.sources.contains_key(Path::new("lib/ds-test/src/test.sol"))); + + // should be installed + if let Some(solc) = Solc::find_svm_installed_version("0.8.10").ok().flatten() { + let out = solc.compile(&input).unwrap(); + assert!(!out.has_error()); + assert!(out.sources.contains_key("lib/ds-test/src/test.sol")); + } +}