diff --git a/ethers-solc/src/remappings.rs b/ethers-solc/src/remappings.rs index 0006d60315..b3eaee2aea 100644 --- a/ethers-solc/src/remappings.rs +++ b/ethers-solc/src/remappings.rs @@ -423,7 +423,7 @@ impl Candidate { /// Which should be resolved to the top level dir `@openzeppelin` /// /// We also treat candidates with a `node_modules` parent directory differently and consider - /// them to `hardhat` style. In which case the trailing library barrier `contracts` will be + /// them to be `hardhat` style. In which case the trailing library barrier `contracts` will be /// stripped from the remapping path. This differs from dapptools style which does not include /// the library barrier path `src` in the solidity import statements. For example, for /// dapptools you could have @@ -472,8 +472,23 @@ impl Candidate { window_start: PathBuf, is_inside_node_modules: bool, ) { - if let Some(pos) = - candidates.iter().position(|c| c.source_dir.ends_with(DAPPTOOLS_CONTRACTS_DIR)) + // if there's only a single source dir candidate then we use this + if let Some(pos) = candidates + .iter() + .enumerate() + .fold((0, None), |(mut contracts_dir_count, mut pos), (idx, c)| { + if c.source_dir.ends_with(DAPPTOOLS_CONTRACTS_DIR) { + contracts_dir_count += 1; + if contracts_dir_count == 1 { + pos = Some(idx) + } else { + pos = None; + } + } + + (contracts_dir_count, pos) + }) + .1 { let c = candidates.remove(pos); *candidates = vec![c]; @@ -630,6 +645,7 @@ fn find_remapping_candidates( } } } + // need to find the actual next window in the event `open` is a lib dir let window_start = next_nested_window(open, current_dir); // finally, we need to merge, adjust candidates from the same level and opening window @@ -1227,4 +1243,37 @@ mod tests { "/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/lib.Z6ODLZJQeJQa/repo1/lib/ds-test" )); } + + #[test] + fn find_openzeppelin_remapping() { + let tmp_dir = tempdir("lib").unwrap(); + let tmp_dir_path = tmp_dir.path(); + let paths = [ + "lib/ds-test/src/test.sol", + "lib/forge-std/src/test.sol", + "openzeppelin/contracts/interfaces/c.sol", + ]; + mkdir_or_touch(tmp_dir_path, &paths[..]); + + let path = tmp_dir_path.display().to_string(); + let mut remappings = Remapping::find_many(path); + remappings.sort_unstable(); + + let mut expected = vec![ + Remapping { + name: "ds-test/".to_string(), + path: to_str(tmp_dir_path.join("lib/ds-test/src")), + }, + Remapping { + name: "openzeppelin/".to_string(), + path: to_str(tmp_dir_path.join("openzeppelin/contracts")), + }, + Remapping { + name: "forge-std/".to_string(), + path: to_str(tmp_dir_path.join("lib/forge-std/src")), + }, + ]; + expected.sort_unstable(); + pretty_assertions::assert_eq!(remappings, expected); + } } diff --git a/ethers-solc/src/utils.rs b/ethers-solc/src/utils.rs index aba0a88346..c8845c0856 100644 --- a/ethers-solc/src/utils.rs +++ b/ethers-solc/src/utils.rs @@ -473,7 +473,7 @@ pub fn create_parent_dir_all(file: impl AsRef) -> Result<(), SolcError> { #[cfg(test)] mod tests { use super::*; - use crate::resolver::Node; + use solang_parser::pt::SourceUnitPart; use std::{ collections::HashSet,