From 8d7369ac44cc87f6e15390cda46aa7be8917f6ef Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Fri, 7 Jan 2022 17:50:29 +0100 Subject: [PATCH 01/21] solc flatten implementation --- ethers-solc/src/lib.rs | 43 +++++++++++++++++++++++++++++++++ ethers-solc/src/project_util.rs | 4 +++ ethers-solc/src/resolver.rs | 39 +++++++++--------------------- ethers-solc/src/utils.rs | 35 ++++++++++++++++++++++++++- ethers-solc/tests/project.rs | 25 +++++++++++++++++++ 5 files changed, 118 insertions(+), 28 deletions(-) diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs index 78a77493a..837fb45dd 100644 --- a/ethers-solc/src/lib.rs +++ b/ethers-solc/src/lib.rs @@ -481,6 +481,49 @@ impl Project { } Ok(()) } + + // Flatten all file imports into a single string + pub fn flatten(&self, target: &PathBuf) -> Result { + tracing::trace!("flattening file"); + let graph = Graph::resolve(&self.paths)?; + + struct Flattener<'a> { + f: &'a dyn Fn(&Flattener, &PathBuf) -> Result>, + } + let flatten = Flattener { + f: &|flattener, target| { + let target_index = graph.files().get(target).ok_or(SolcError::msg(format!( + "cannot resolve file at \"{:?}\"", + target.display() + )))?; + let target_node = graph.node(*target_index); + let node_dir = target.parent().ok_or(SolcError::msg(format!( + "failed to get parent directory for \"{:?}\"", + target.display() + )))?; + Ok(utils::RE_SOL_IMPORT.replace_all( + target_node.content(), + |cap: ®ex::Captures<'_>| { + let import = cap.name("p1").or(cap.name("p2")).or(cap.name("p3")).unwrap(); // one of the name patterns must match + + let import_path = utils::resolve_import_component( + &PathBuf::from(import.as_str()), + node_dir, + &self.paths, + ) + .expect("failed to resolve import component {}"); + + let result = (flattener.f)(flattener, &import_path) + .expect("failed to flatten the import file"); + utils::RE_SOL_PRAGMA_VERSION.replace_all(&result, "").trim().to_owned() + }, + )) + }, + }; + + let flattenned = (flatten.f)(&flatten, target)?; + Ok(flattenned.to_string()) + } } enum PreprocessedJob { diff --git a/ethers-solc/src/project_util.rs b/ethers-solc/src/project_util.rs index e2839fd5d..f45785d38 100644 --- a/ethers-solc/src/project_util.rs +++ b/ethers-solc/src/project_util.rs @@ -40,6 +40,10 @@ impl TempProject { self.project().compile() } + pub fn flatten(&self, target: &PathBuf) -> Result { + self.project().flatten(target) + } + pub fn project_mut(&mut self) -> &mut Project { &mut self.inner } diff --git a/ethers-solc/src/resolver.rs b/ethers-solc/src/resolver.rs index dae6c7e74..a7a94d734 100644 --- a/ethers-solc/src/resolver.rs +++ b/ethers-solc/src/resolver.rs @@ -28,7 +28,7 @@ use std::{ collections::{HashMap, VecDeque}, - path::{Component, Path, PathBuf}, + path::{Path, PathBuf}, }; use rayon::prelude::*; @@ -139,33 +139,12 @@ impl Graph { }; for import in node.data.imports.iter() { - let component = match import.components().next() { - Some(inner) => inner, - None => continue, - }; - if component == Component::CurDir || component == Component::ParentDir { - // if the import is relative we assume it's already part of the processed input - // file set - match utils::canonicalize(node_dir.join(import)) { - Ok(target) => { - // the file at least exists, - add_node(&mut unresolved, &mut index, &mut resolved_imports, target)?; - } - Err(err) => { - tracing::trace!("failed to resolve relative import \"{:?}\"", err); - } - } - } else { - // resolve library file - if let Some(lib) = paths.resolve_library_import(import.as_ref()) { - add_node(&mut unresolved, &mut index, &mut resolved_imports, lib)?; - } else { - tracing::trace!( - "failed to resolve library import \"{:?}\"", - import.display() - ); + match utils::resolve_import_component(import, node_dir, paths) { + Ok(result) => { + add_node(&mut unresolved, &mut index, &mut resolved_imports, result)?; } - } + Err(err) => tracing::trace!("failed to resolve import component \"{:?}\"", err), + }; } nodes.push(node); edges.push(resolved_imports); @@ -409,6 +388,12 @@ pub struct Node { data: SolData, } +impl Node { + pub fn content(&self) -> &str { + &self.source.content + } +} + #[derive(Debug, Clone)] #[allow(unused)] struct SolData { diff --git a/ethers-solc/src/utils.rs b/ethers-solc/src/utils.rs index 49a0e4677..3edbe28b8 100644 --- a/ethers-solc/src/utils.rs +++ b/ethers-solc/src/utils.rs @@ -2,7 +2,10 @@ use std::path::{Component, Path, PathBuf}; -use crate::{error::SolcError, SolcIoError}; +use crate::{ + error::{self, SolcError}, + ProjectPathsConfig, SolcIoError, +}; use once_cell::sync::Lazy; use regex::Regex; use semver::Version; @@ -79,6 +82,36 @@ pub fn canonicalize(path: impl AsRef) -> Result { dunce::canonicalize(&path).map_err(|err| SolcIoError::new(err, path)) } +pub fn resolve_import_component( + import: &PathBuf, + node_dir: &Path, + paths: &ProjectPathsConfig, +) -> error::Result { + let component = match import.components().next() { + Some(inner) => inner, + None => { + return Err(SolcError::msg(format!( + "failed to resolve import at \"{:?}\"", + 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 + canonicalize(node_dir.join(import)).map_err(|err| err.into()) + } else { + // resolve library file + match paths.resolve_library_import(import.as_ref()) { + Some(lib) => Ok(lib), + None => Err(SolcError::msg(format!( + "failed to resolve library import \"{:?}\"", + import.display() + ))), + } + } +} + /// Returns the path to the library if the source path is in fact determined to be a library path, /// and it exists. /// Note: this does not handle relative imports or remappings. diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs index 86c2d290e..2a081375a 100644 --- a/ethers-solc/tests/project.rs +++ b/ethers-solc/tests/project.rs @@ -225,3 +225,28 @@ fn copy_dir_all(src: impl AsRef, dst: impl AsRef) -> io::Result<()> } Ok(()) } + +#[test] +fn can_flatten_file() { + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/test-contract-libs"); + let target = root.join("src").join("Foo.sol"); + let paths = ProjectPathsConfig::builder() + .sources(root.join("src")) + .lib(root.join("lib1")) + .lib(root.join("lib2")); + let project = TempProject::::new(paths).unwrap(); + + assert!(project.flatten(&target).is_ok()); +} + +#[test] +fn can_flatten_file_with_external_lib() { + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/hardhat-sample"); + let target = root.join("contracts").join("Greeter.sol"); + let paths = ProjectPathsConfig::builder() + .sources(root.join("contracts")) + .lib(root.join("node_modules")); + let project = TempProject::::new(paths).unwrap(); + + assert!(project.flatten(&target).is_ok()); +} From f33b5c22aa3f64d5eb07ca51f685b3b1d5efe1d1 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Fri, 7 Jan 2022 18:41:08 +0100 Subject: [PATCH 02/21] upd changelog --- CHANGELOG.md | 2 ++ ethers-solc/src/lib.rs | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23d8738a6..c3be326d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ ### Unreleased +- Add ability to flatten file imports + [#774](https://github.com/gakonst/ethers-rs/pull/774) - Add dependency graph and resolve all imported libraryfiles [#750](https://github.com/gakonst/ethers-rs/pull/750) - `Remapping::find_many` does not return a `Result` anymore diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs index 837fb45dd..75ea0e3b4 100644 --- a/ethers-solc/src/lib.rs +++ b/ethers-solc/src/lib.rs @@ -501,7 +501,7 @@ impl Project { "failed to get parent directory for \"{:?}\"", target.display() )))?; - Ok(utils::RE_SOL_IMPORT.replace_all( + let flattened = utils::RE_SOL_IMPORT.replace_all( target_node.content(), |cap: ®ex::Captures<'_>| { let import = cap.name("p1").or(cap.name("p2")).or(cap.name("p3")).unwrap(); // one of the name patterns must match @@ -517,12 +517,12 @@ impl Project { .expect("failed to flatten the import file"); utils::RE_SOL_PRAGMA_VERSION.replace_all(&result, "").trim().to_owned() }, - )) + ); + Ok(flattened) }, }; - let flattenned = (flatten.f)(&flatten, target)?; - Ok(flattenned.to_string()) + Ok((flatten.f)(&flatten, target)?.to_string()) } } From 1742630d5f09e6dc0e88bbc6b0ba37cfb7666f07 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Fri, 7 Jan 2022 18:42:26 +0100 Subject: [PATCH 03/21] upd docs --- ethers-solc/src/lib.rs | 2 +- ethers-solc/src/utils.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs index 75ea0e3b4..70da8fff7 100644 --- a/ethers-solc/src/lib.rs +++ b/ethers-solc/src/lib.rs @@ -482,7 +482,7 @@ impl Project { Ok(()) } - // Flatten all file imports into a single string + /// Flatten all file imports into a single string pub fn flatten(&self, target: &PathBuf) -> Result { tracing::trace!("flattening file"); let graph = Graph::resolve(&self.paths)?; diff --git a/ethers-solc/src/utils.rs b/ethers-solc/src/utils.rs index 3edbe28b8..43ab7d4ba 100644 --- a/ethers-solc/src/utils.rs +++ b/ethers-solc/src/utils.rs @@ -82,6 +82,7 @@ pub fn canonicalize(path: impl AsRef) -> Result { dunce::canonicalize(&path).map_err(|err| SolcIoError::new(err, path)) } +/// Try to resolve import to a local file or library path pub fn resolve_import_component( import: &PathBuf, node_dir: &Path, From 85dfd6fe39a7e2e7a0ff52e22bb080316acaca57 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Sun, 9 Jan 2022 12:44:35 +0100 Subject: [PATCH 04/21] revamp flattening --- Cargo.lock | 4 +- ethers-solc/src/artifacts.rs | 2 +- ethers-solc/src/cache.rs | 4 +- ethers-solc/src/compile.rs | 2 +- ethers-solc/src/config.rs | 67 ++++++++++++++++++++++++++++++- ethers-solc/src/lib.rs | 41 +------------------ ethers-solc/src/resolver.rs | 77 ++++++++++++++++++++++++++---------- ethers-solc/src/utils.rs | 16 ++++---- 8 files changed, 136 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40201f11a..0c6fbe072 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3390,8 +3390,8 @@ dependencies = [ [[package]] name = "solang-parser" -version = "0.1.0" -source = "git+https://github.com/hyperledger-labs/solang#d92c43a7257009022016d6611255a5ce572852df" +version = "0.1.1" +source = "git+https://github.com/hyperledger-labs/solang#190f7205dd2ec50f8d930741785a79e6ddf0148b" dependencies = [ "lalrpop", "lalrpop-util", diff --git a/ethers-solc/src/artifacts.rs b/ethers-solc/src/artifacts.rs index 809dcef34..84496fc8b 100644 --- a/ethers-solc/src/artifacts.rs +++ b/ethers-solc/src/artifacts.rs @@ -473,7 +473,7 @@ impl Source { /// Returns all import statements of the file pub fn parse_imports(&self) -> Vec<&str> { - utils::find_import_paths(self.as_ref()) + utils::find_import_paths(self.as_ref()).map(|m| m.as_str()).collect() } } diff --git a/ethers-solc/src/cache.rs b/ethers-solc/src/cache.rs index 6dd241e69..1999b305d 100644 --- a/ethers-solc/src/cache.rs +++ b/ethers-solc/src/cache.rs @@ -274,10 +274,10 @@ impl SolFilesCacheBuilder { .map_err(|err| SolcError::solc(err.to_string()))? .as_millis() as u64; let imports = - utils::find_import_paths(source.as_ref()).into_iter().map(str::to_string).collect(); + utils::find_import_paths(source.as_ref()).map(|m| m.as_str().to_owned()).collect(); let version_pragmas = utils::find_version_pragma(source.as_ref()) - .map(|v| vec![v.to_string()]) + .map(|v| vec![v.as_str().to_string()]) .unwrap_or_default(); let entry = CacheEntry { diff --git a/ethers-solc/src/compile.rs b/ethers-solc/src/compile.rs index 2e68cb598..ddf031aea 100644 --- a/ethers-solc/src/compile.rs +++ b/ethers-solc/src/compile.rs @@ -323,7 +323,7 @@ impl Solc { pub fn source_version_req(source: &Source) -> Result { let version = utils::find_version_pragma(&source.content).ok_or(SolcError::PragmaNotFound)?; - Self::version_req(version) + Self::version_req(version.as_str()) } /// Returns the corresponding SemVer version requirement for the solidity version diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs index ac76ab896..38134d9e5 100644 --- a/ethers-solc/src/config.rs +++ b/ethers-solc/src/config.rs @@ -4,6 +4,7 @@ use crate::{ error::{Result, SolcError, SolcIoError}, hh::HardhatArtifact, remappings::Remapping, + resolver::Graph, utils, CompilerOutput, Source, Sources, }; use ethers_core::{abi::Abi, types::Bytes}; @@ -11,8 +12,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{ collections::BTreeMap, convert::TryFrom, - fmt, - fmt::Formatter, + fmt::{self, Formatter}, fs, io, path::{Path, PathBuf}, }; @@ -146,6 +146,69 @@ impl ProjectPathsConfig { pub fn find_libs(root: impl AsRef) -> Vec { vec![utils::find_fave_or_alt_path(root, "lib", "node_modules")] } + + /// Flatten all file imports into a single string + pub fn flatten(&self, target: &PathBuf) -> Result { + tracing::trace!("flattening file"); + let graph = Graph::resolve(&self)?; + + struct Flattener<'a> { + f: &'a dyn Fn(&Flattener, &PathBuf) -> Result, + } + let flatten = Flattener { + f: &|flattener, target| { + let target_dir = target.parent().ok_or(SolcError::msg(format!( + "failed to get parent directory for \"{:?}\"", + target.display() + )))?; + let target_index = graph.files().get(target).ok_or(SolcError::msg(format!( + "cannot resolve file at \"{:?}\"", + target.display() + )))?; + let target_node = graph.node(*target_index); + + let mut imports = target_node.imports().clone(); + imports.sort_by(|a, b| b.loc().0.cmp(&a.loc().0)); + + let content = target_node.content().bytes().collect::>(); + let mut extended = vec![]; + let mut curr_import = imports.pop(); + + let mut i = 0; + while i < content.len() { + if let Some(ref import) = curr_import { + let (start, end) = import.loc(); + if i == start { + let import_path = + utils::resolve_import_component(import.path(), target_dir, &self)?; + let import_content = (flattener.f)(flattener, &import_path)?; + let import_content = + utils::RE_SOL_PRAGMA_VERSION.replace_all(&import_content, ""); + extended.extend(import_content.trim().as_bytes()); + i = end; + curr_import = imports.pop(); + continue + } + } + + extended.push(content[i]); + i += 1; + } + + let result = String::from_utf8(extended).map_err(|err| { + SolcError::msg(format!( + "failed to convert extended bytes to string: {}", + err.to_string() + )) + })?; + + Ok(result) + }, + }; + + let flattened = (flatten.f)(&flatten, target)?; + Ok(flattened.to_string()) + } } impl fmt::Display for ProjectPathsConfig { diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs index 70da8fff7..15fcef862 100644 --- a/ethers-solc/src/lib.rs +++ b/ethers-solc/src/lib.rs @@ -482,47 +482,8 @@ impl Project { Ok(()) } - /// Flatten all file imports into a single string pub fn flatten(&self, target: &PathBuf) -> Result { - tracing::trace!("flattening file"); - let graph = Graph::resolve(&self.paths)?; - - struct Flattener<'a> { - f: &'a dyn Fn(&Flattener, &PathBuf) -> Result>, - } - let flatten = Flattener { - f: &|flattener, target| { - let target_index = graph.files().get(target).ok_or(SolcError::msg(format!( - "cannot resolve file at \"{:?}\"", - target.display() - )))?; - let target_node = graph.node(*target_index); - let node_dir = target.parent().ok_or(SolcError::msg(format!( - "failed to get parent directory for \"{:?}\"", - target.display() - )))?; - let flattened = utils::RE_SOL_IMPORT.replace_all( - target_node.content(), - |cap: ®ex::Captures<'_>| { - let import = cap.name("p1").or(cap.name("p2")).or(cap.name("p3")).unwrap(); // one of the name patterns must match - - let import_path = utils::resolve_import_component( - &PathBuf::from(import.as_str()), - node_dir, - &self.paths, - ) - .expect("failed to resolve import component {}"); - - let result = (flattener.f)(flattener, &import_path) - .expect("failed to flatten the import file"); - utils::RE_SOL_PRAGMA_VERSION.replace_all(&result, "").trim().to_owned() - }, - ); - Ok(flattened) - }, - }; - - Ok((flatten.f)(&flatten, target)?.to_string()) + self.paths.flatten(target) } } diff --git a/ethers-solc/src/resolver.rs b/ethers-solc/src/resolver.rs index a7a94d734..7d9cce429 100644 --- a/ethers-solc/src/resolver.rs +++ b/ethers-solc/src/resolver.rs @@ -32,8 +32,9 @@ use std::{ }; use rayon::prelude::*; +use regex::Match; use semver::VersionReq; -use solang_parser::pt::{Import, SourceUnitPart}; +use solang_parser::pt::{Import, Loc, SourceUnitPart}; use crate::{error::Result, utils, ProjectPathsConfig, Solc, Source, Sources}; @@ -139,7 +140,7 @@ impl Graph { }; for import in node.data.imports.iter() { - match utils::resolve_import_component(import, node_dir, paths) { + match utils::resolve_import_component(import.path(), node_dir, paths) { Ok(result) => { add_node(&mut unresolved, &mut index, &mut resolved_imports, result)?; } @@ -392,6 +393,10 @@ impl Node { pub fn content(&self) -> &str { &self.source.content } + + pub fn imports(&self) -> &Vec { + &self.data.imports + } } #[derive(Debug, Clone)] @@ -399,7 +404,41 @@ impl Node { struct SolData { version: Option, version_req: Option, - imports: Vec, + imports: Vec, +} + +#[derive(Debug, Clone)] +pub struct SolImport { + path: PathBuf, + loc: Location, +} + +#[derive(Debug, Clone)] +pub struct Location { + pub start: usize, + pub end: usize, +} + +impl SolImport { + pub fn path(&self) -> &PathBuf { + &self.path + } + + pub fn loc(&self) -> (usize, usize) { + (self.loc.start, self.loc.end) + } +} + +impl From> for Location { + fn from(src: Match) -> Self { + Location { start: src.start(), end: src.end() } + } +} + +impl From for Location { + fn from(src: Loc) -> Self { + Location { start: src.1, end: src.2 } + } } fn read_node(file: impl AsRef) -> Result { @@ -415,7 +454,7 @@ fn read_node(file: impl AsRef) -> Result { /// parsing fails, we'll fall back to extract that info via regex fn parse_data(content: &str) -> SolData { let mut version = None; - let mut imports = Vec::new(); + let mut imports = Vec::::new(); match solang_parser::parse(content, 0) { Ok(units) => { for unit in units.0 { @@ -427,12 +466,15 @@ fn parse_data(content: &str) -> SolData { } } SourceUnitPart::ImportDirective(_, import) => { - let import = match import { - Import::Plain(s) => s, - Import::GlobalSymbol(s, _) => s, - Import::Rename(s, _) => s, + let (import, loc) = match import { + Import::Plain(s, l) => (s, l), + Import::GlobalSymbol(s, _, l) => (s, l), + Import::Rename(s, _, l) => (s, l), }; - imports.push(PathBuf::from(import.string)); + imports.push(SolImport { + path: PathBuf::from(import.string), + loc: loc.into(), + }); } _ => {} } @@ -443,21 +485,19 @@ fn parse_data(content: &str) -> SolData { "failed to parse solidity ast: \"{:?}\". Falling back to regex to extract data", err ); - version = utils::find_version_pragma(content).map(str::to_string); + version = utils::find_version_pragma(content).map(|m| m.as_str().to_owned()); imports = utils::find_import_paths(content) - .into_iter() - .map(|p| Path::new(p).to_path_buf()) - .collect() + .map(|m| SolImport { path: PathBuf::from(m.as_str()), loc: m.into() }) + .collect(); } }; - let version_req = if let Some(ref v) = version { Solc::version_req(v).ok() } else { None }; + let version_req = version.as_ref().map(|v| Solc::version_req(&v).ok()).flatten(); SolData { version_req, version, imports } } #[cfg(test)] mod tests { use super::*; - use std::path::Path; #[test] fn can_resolve_hardhat_dependency_graph() { @@ -499,11 +539,8 @@ mod tests { let dapp_test = graph.node(1); assert_eq!(dapp_test.path, paths.sources.join("Dapp.t.sol")); assert_eq!( - dapp_test.data.imports, - vec![ - Path::new("ds-test/test.sol").to_path_buf(), - Path::new("./Dapp.sol").to_path_buf() - ] + dapp_test.data.imports.iter().map(|i| i.path()).collect::>(), + vec![&PathBuf::from("ds-test/test.sol"), &PathBuf::from("./Dapp.sol")] ); assert_eq!(graph.imported_nodes(1).to_vec(), vec![2, 0]); } diff --git a/ethers-solc/src/utils.rs b/ethers-solc/src/utils.rs index 43ab7d4ba..c8da1eb84 100644 --- a/ethers-solc/src/utils.rs +++ b/ethers-solc/src/utils.rs @@ -7,7 +7,7 @@ use crate::{ ProjectPathsConfig, SolcIoError, }; use once_cell::sync::Lazy; -use regex::Regex; +use regex::{Match, Regex}; use semver::Version; use tiny_keccak::{Hasher, Keccak}; use walkdir::WalkDir; @@ -30,18 +30,16 @@ pub static RE_SOL_PRAGMA_VERSION: Lazy = /// `import "./contracts/Contract.sol";` -> `"./contracts/Contract.sol"`. /// /// See also https://docs.soliditylang.org/en/v0.8.9/grammar.html -pub fn find_import_paths(contract: &str) -> Vec<&str> { +pub fn find_import_paths(contract: &str) -> impl Iterator { RE_SOL_IMPORT .captures_iter(contract) - .filter_map(|cap| cap.name("p1").or_else(|| cap.name("p2")).or_else(|| cap.name("p3"))) - .map(|m| m.as_str()) - .collect() + .filter_map(|cap| cap.name("p1").or(cap.name("p2")).or(cap.name("p3"))) } /// Returns the solidity version pragma from the given input: /// `pragma solidity ^0.5.2;` => `^0.5.2` -pub fn find_version_pragma(contract: &str) -> Option<&str> { - RE_SOL_PRAGMA_VERSION.captures(contract)?.name("version").map(|m| m.as_str()) +pub fn find_version_pragma(contract: &str) -> Option { + RE_SOL_PRAGMA_VERSION.captures(contract)?.name("version") } /// Returns a list of absolute paths to all the solidity files under the root, or the file itself, @@ -333,7 +331,7 @@ import { T } from '../Test2.sol'; "##; assert_eq!( vec!["hardhat/console.sol", "../contract/Contract.sol", "../Test.sol", "../Test2.sol"], - find_import_paths(s) + find_import_paths(s).map(|m| m.as_str()).collect::>() ); } #[test] @@ -341,7 +339,7 @@ import { T } from '../Test2.sol'; let s = r##"//SPDX-License-Identifier: Unlicense pragma solidity ^0.8.0; "##; - assert_eq!(Some("^0.8.0"), find_version_pragma(s)); + assert_eq!(Some("^0.8.0"), find_version_pragma(s).map(|s| s.as_str())); } #[test] From 05edae5f938b0033267a6d94c45eab539918904b Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Sun, 9 Jan 2022 13:21:09 +0100 Subject: [PATCH 05/21] clippy --- ethers-solc/src/config.rs | 34 ++++++++++++++++----------------- ethers-solc/src/lib.rs | 10 +++++++--- ethers-solc/src/project_util.rs | 2 +- ethers-solc/src/resolver.rs | 2 +- ethers-solc/src/utils.rs | 4 ++-- 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs index 6fb567603..77290c1aa 100644 --- a/ethers-solc/src/config.rs +++ b/ethers-solc/src/config.rs @@ -148,31 +148,32 @@ impl ProjectPathsConfig { } /// Flatten all file imports into a single string - pub fn flatten(&self, target: &PathBuf) -> Result { + pub fn flatten(&self, target: &Path) -> Result { tracing::trace!("flattening file"); - let graph = Graph::resolve(&self)?; + let graph = Graph::resolve(self)?; struct Flattener<'a> { - f: &'a dyn Fn(&Flattener, &PathBuf) -> Result, + f: &'a dyn Fn(&Flattener, &Path) -> Result, } let flatten = Flattener { f: &|flattener, target| { - let target_dir = target.parent().ok_or(SolcError::msg(format!( - "failed to get parent directory for \"{:?}\"", - target.display() - )))?; - let target_index = graph.files().get(target).ok_or(SolcError::msg(format!( - "cannot resolve file at \"{:?}\"", - target.display() - )))?; + let target_dir = target.parent().ok_or_else(|| { + SolcError::msg(format!( + "failed to get parent directory for \"{:?}\"", + target.display() + )) + })?; + let target_index = graph.files().get(target).ok_or_else(|| { + SolcError::msg(format!("cannot resolve file at \"{:?}\"", target.display())) + })?; let target_node = graph.node(*target_index); let mut imports = target_node.imports().clone(); imports.sort_by(|a, b| b.loc().0.cmp(&a.loc().0)); let content = target_node.content().bytes().collect::>(); - let mut extended = vec![]; let mut curr_import = imports.pop(); + let mut extended = vec![]; let mut i = 0; while i < content.len() { @@ -180,7 +181,7 @@ impl ProjectPathsConfig { let (start, end) = import.loc(); if i == start { let import_path = - utils::resolve_import_component(import.path(), target_dir, &self)?; + utils::resolve_import_component(import.path(), target_dir, self)?; let import_content = (flattener.f)(flattener, &import_path)?; let import_content = utils::RE_SOL_PRAGMA_VERSION.replace_all(&import_content, ""); @@ -196,10 +197,7 @@ impl ProjectPathsConfig { } let result = String::from_utf8(extended).map_err(|err| { - SolcError::msg(format!( - "failed to convert extended bytes to string: {}", - err.to_string() - )) + SolcError::msg(format!("failed to convert extended bytes to string: {}", err)) })?; Ok(result) @@ -207,7 +205,7 @@ impl ProjectPathsConfig { }; let flattened = (flatten.f)(&flatten, target)?; - Ok(flattened.to_string()) + Ok(flattened) } } diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs index b0b20a86f..b36a800cb 100644 --- a/ethers-solc/src/lib.rs +++ b/ethers-solc/src/lib.rs @@ -37,8 +37,12 @@ use crate::{ }; use error::Result; use std::{ - borrow::Cow, collections::BTreeMap, convert::TryInto, fmt, fs, marker::PhantomData, - path::PathBuf, + borrow::Cow, + collections::BTreeMap, + convert::TryInto, + fmt, fs, + marker::PhantomData, + path::{Path, PathBuf}, }; /// Utilities for creating, mocking and testing of (temporary) projects @@ -482,7 +486,7 @@ impl Project { Ok(()) } - pub fn flatten(&self, target: &PathBuf) -> Result { + pub fn flatten(&self, target: &Path) -> Result { self.paths.flatten(target) } } diff --git a/ethers-solc/src/project_util.rs b/ethers-solc/src/project_util.rs index f45785d38..7b48b2c28 100644 --- a/ethers-solc/src/project_util.rs +++ b/ethers-solc/src/project_util.rs @@ -40,7 +40,7 @@ impl TempProject { self.project().compile() } - pub fn flatten(&self, target: &PathBuf) -> Result { + pub fn flatten(&self, target: &Path) -> Result { self.project().flatten(target) } diff --git a/ethers-solc/src/resolver.rs b/ethers-solc/src/resolver.rs index acdbebbee..24d339537 100644 --- a/ethers-solc/src/resolver.rs +++ b/ethers-solc/src/resolver.rs @@ -499,7 +499,7 @@ fn parse_data(content: &str) -> SolData { .collect(); } }; - let version_req = version.as_ref().map(|v| Solc::version_req(&v).ok()).flatten(); + let version_req = version.as_ref().and_then(|v| Solc::version_req(v).ok()); SolData { version_req, version, imports } } diff --git a/ethers-solc/src/utils.rs b/ethers-solc/src/utils.rs index c8da1eb84..9532c849a 100644 --- a/ethers-solc/src/utils.rs +++ b/ethers-solc/src/utils.rs @@ -33,7 +33,7 @@ pub static RE_SOL_PRAGMA_VERSION: Lazy = pub fn find_import_paths(contract: &str) -> impl Iterator { RE_SOL_IMPORT .captures_iter(contract) - .filter_map(|cap| cap.name("p1").or(cap.name("p2")).or(cap.name("p3"))) + .filter_map(|cap| cap.name("p1").or_else(|| cap.name("p2")).or_else(|| cap.name("p3"))) } /// Returns the solidity version pragma from the given input: @@ -82,7 +82,7 @@ pub fn canonicalize(path: impl AsRef) -> Result { /// Try to resolve import to a local file or library path pub fn resolve_import_component( - import: &PathBuf, + import: &Path, node_dir: &Path, paths: &ProjectPathsConfig, ) -> error::Result { From 9b268d2577bcb3e983c6d24227337b6f9c5d3f56 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Tue, 11 Jan 2022 23:38:37 +0100 Subject: [PATCH 06/21] use resolve_import method --- ethers-solc/src/config.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs index d0aae6aa9..ede209c22 100644 --- a/ethers-solc/src/config.rs +++ b/ethers-solc/src/config.rs @@ -172,7 +172,7 @@ impl ProjectPathsConfig { vec![utils::find_fave_or_alt_path(root, "lib", "node_modules")] } - /// Flatten all file imports into a single string + /// Flattens all file imports into a single string pub fn flatten(&self, target: &Path) -> Result { tracing::trace!("flattening file"); let graph = Graph::resolve(self)?; @@ -205,8 +205,7 @@ impl ProjectPathsConfig { if let Some(ref import) = curr_import { let (start, end) = import.loc(); if i == start { - let import_path = - utils::resolve_import_component(import.path(), target_dir, self)?; + let import_path = self.resolve_import(target_dir, import.path())?; let import_content = (flattener.f)(flattener, &import_path)?; let import_content = utils::RE_SOL_PRAGMA_VERSION.replace_all(&import_content, ""); From 57388f2f2bef39bb0ee101d605d0b3eb59dc793a Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Wed, 12 Jan 2022 21:58:00 +0100 Subject: [PATCH 07/21] extract recursive flatenning into a separate func --- ethers-solc/src/config.rs | 90 +++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 50 deletions(-) diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs index ede209c22..d1a2f6e2e 100644 --- a/ethers-solc/src/config.rs +++ b/ethers-solc/src/config.rs @@ -176,60 +176,50 @@ impl ProjectPathsConfig { pub fn flatten(&self, target: &Path) -> Result { tracing::trace!("flattening file"); let graph = Graph::resolve(self)?; - - struct Flattener<'a> { - f: &'a dyn Fn(&Flattener, &Path) -> Result, - } - let flatten = Flattener { - f: &|flattener, target| { - let target_dir = target.parent().ok_or_else(|| { - SolcError::msg(format!( - "failed to get parent directory for \"{:?}\"", - target.display() - )) - })?; - let target_index = graph.files().get(target).ok_or_else(|| { - SolcError::msg(format!("cannot resolve file at \"{:?}\"", target.display())) - })?; - let target_node = graph.node(*target_index); - - let mut imports = target_node.imports().clone(); - imports.sort_by(|a, b| b.loc().0.cmp(&a.loc().0)); - - let content = target_node.content().bytes().collect::>(); - let mut curr_import = imports.pop(); - let mut extended = vec![]; - - let mut i = 0; - while i < content.len() { - if let Some(ref import) = curr_import { - let (start, end) = import.loc(); - if i == start { - let import_path = self.resolve_import(target_dir, import.path())?; - let import_content = (flattener.f)(flattener, &import_path)?; - let import_content = - utils::RE_SOL_PRAGMA_VERSION.replace_all(&import_content, ""); - extended.extend(import_content.trim().as_bytes()); - i = end; - curr_import = imports.pop(); - continue - } - } - - extended.push(content[i]); - i += 1; + Ok(self.flatten_file(target, &graph)?) + } + + fn flatten_file(&self, target: &Path, graph: &Graph) -> Result { + let target_dir = target.parent().ok_or_else(|| { + SolcError::msg(format!("failed to get parent directory for \"{:?}\"", target.display())) + })?; + let target_index = graph.files().get(target).ok_or_else(|| { + SolcError::msg(format!("cannot resolve file at \"{:?}\"", target.display())) + })?; + let target_node = graph.node(*target_index); + + let mut imports = target_node.imports().clone(); + imports.sort_by(|a, b| b.loc().0.cmp(&a.loc().0)); + + let content = target_node.content().bytes().collect::>(); + let mut curr_import = imports.pop(); + let mut extended = vec![]; + + let mut ptr = 0; + while ptr < content.len() { + if let Some(ref import) = curr_import { + let (start, end) = import.loc(); + if ptr == start { + let import_path = self.resolve_import(target_dir, import.path())?; + let import_content = self.flatten_file(&import_path, graph)?; + let import_content = + utils::RE_SOL_PRAGMA_VERSION.replace_all(&import_content, ""); + extended.extend(import_content.trim().as_bytes()); + ptr = end; + curr_import = imports.pop(); + continue } + } - let result = String::from_utf8(extended).map_err(|err| { - SolcError::msg(format!("failed to convert extended bytes to string: {}", err)) - })?; + extended.push(content[ptr]); + ptr += 1; + } - Ok(result) - }, - }; + let result = String::from_utf8(extended).map_err(|err| { + SolcError::msg(format!("failed to convert extended bytes to string: {}", err)) + })?; - let flattened = (flatten.f)(&flatten, target)?; - Ok(flattened) + Ok(result) } } From 0ce67a0bc2af083817f8589c92c09adb6d7b5b72 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Wed, 12 Jan 2022 22:53:05 +0100 Subject: [PATCH 08/21] change content iteration for flatten --- ethers-solc/src/config.rs | 49 +++++++++++++++++-------------------- ethers-solc/src/resolver.rs | 10 ++++++++ 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs index d1a2f6e2e..79ed49d24 100644 --- a/ethers-solc/src/config.rs +++ b/ethers-solc/src/config.rs @@ -176,10 +176,10 @@ impl ProjectPathsConfig { pub fn flatten(&self, target: &Path) -> Result { tracing::trace!("flattening file"); let graph = Graph::resolve(self)?; - Ok(self.flatten_file(target, &graph)?) + Ok(self.flatten_node(target, &graph)?) } - fn flatten_file(&self, target: &Path, graph: &Graph) -> Result { + fn flatten_node(&self, target: &Path, graph: &Graph) -> Result { let target_dir = target.parent().ok_or_else(|| { SolcError::msg(format!("failed to get parent directory for \"{:?}\"", target.display())) })?; @@ -189,33 +189,28 @@ impl ProjectPathsConfig { let target_node = graph.node(*target_index); let mut imports = target_node.imports().clone(); - imports.sort_by(|a, b| b.loc().0.cmp(&a.loc().0)); - - let content = target_node.content().bytes().collect::>(); - let mut curr_import = imports.pop(); - let mut extended = vec![]; - - let mut ptr = 0; - while ptr < content.len() { - if let Some(ref import) = curr_import { - let (start, end) = import.loc(); - if ptr == start { - let import_path = self.resolve_import(target_dir, import.path())?; - let import_content = self.flatten_file(&import_path, graph)?; - let import_content = - utils::RE_SOL_PRAGMA_VERSION.replace_all(&import_content, ""); - extended.extend(import_content.trim().as_bytes()); - ptr = end; - curr_import = imports.pop(); - continue - } - } - - extended.push(content[ptr]); - ptr += 1; + imports.sort_by(|a, b| a.loc().0.cmp(&b.loc().0)); + + let mut content = target_node.content().bytes().collect::>(); + let mut offset = 0_isize; + + for import in imports.iter() { + let import_path = self.resolve_import(target_dir, import.path())?; + let import_content = self.flatten_node(&import_path, graph)?; + let import_content = utils::RE_SOL_PRAGMA_VERSION + .replace_all(&import_content, "") + .trim() + .as_bytes() + .to_owned(); + let import_content_len = import_content.len() as isize; + let (start, end) = + import.loc_by_offset(offset).expect("failed to determine import location"); + content.splice(start..end, import_content); + let import_offset = import_content_len - ((end - start) as isize); + offset += import_offset; } - let result = String::from_utf8(extended).map_err(|err| { + let result = String::from_utf8(content).map_err(|err| { SolcError::msg(format!("failed to convert extended bytes to string: {}", err)) })?; diff --git a/ethers-solc/src/resolver.rs b/ethers-solc/src/resolver.rs index fa8a2f98b..2c9dff7d1 100644 --- a/ethers-solc/src/resolver.rs +++ b/ethers-solc/src/resolver.rs @@ -28,6 +28,7 @@ use std::{ collections::{HashMap, VecDeque}, + convert::{TryFrom, TryInto}, path::{Path, PathBuf}, }; @@ -434,6 +435,15 @@ impl SolImport { pub fn loc(&self) -> (usize, usize) { (self.loc.start, self.loc.end) } + + pub fn loc_by_offset( + &self, + offset: isize, + ) -> std::result::Result<(usize, usize), std::num::TryFromIntError> { + let (start, end) = + (isize::try_from(self.loc.start)? + offset, isize::try_from(self.loc.end)? + offset); + Ok((start.try_into()?, end.try_into()?)) + } } impl From> for Location { From 4fa958f31c6d42411418295d3d712dc239611630 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Wed, 12 Jan 2022 22:54:09 +0100 Subject: [PATCH 09/21] remove redundant result --- ethers-solc/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs index 79ed49d24..271612bd1 100644 --- a/ethers-solc/src/config.rs +++ b/ethers-solc/src/config.rs @@ -176,7 +176,7 @@ impl ProjectPathsConfig { pub fn flatten(&self, target: &Path) -> Result { tracing::trace!("flattening file"); let graph = Graph::resolve(self)?; - Ok(self.flatten_node(target, &graph)?) + self.flatten_node(target, &graph) } fn flatten_node(&self, target: &Path, graph: &Graph) -> Result { From 3ea64ad5eb338fcc5166d60351f91b9b29127275 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Wed, 12 Jan 2022 22:55:19 +0100 Subject: [PATCH 10/21] clean up solimport --- ethers-solc/src/resolver.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ethers-solc/src/resolver.rs b/ethers-solc/src/resolver.rs index 2c9dff7d1..02525c161 100644 --- a/ethers-solc/src/resolver.rs +++ b/ethers-solc/src/resolver.rs @@ -28,7 +28,7 @@ use std::{ collections::{HashMap, VecDeque}, - convert::{TryFrom, TryInto}, + convert::TryInto, path::{Path, PathBuf}, }; @@ -440,8 +440,7 @@ impl SolImport { &self, offset: isize, ) -> std::result::Result<(usize, usize), std::num::TryFromIntError> { - let (start, end) = - (isize::try_from(self.loc.start)? + offset, isize::try_from(self.loc.end)? + offset); + let (start, end) = (self.loc.start as isize + offset, self.loc.end as isize + offset); Ok((start.try_into()?, end.try_into()?)) } } From a19081c0ff9f696f959d3121cf5095aab11df043 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Wed, 12 Jan 2022 22:58:38 +0100 Subject: [PATCH 11/21] add comment to project.flatten --- ethers-solc/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs index 1aac656aa..117a283cd 100644 --- a/ethers-solc/src/lib.rs +++ b/ethers-solc/src/lib.rs @@ -512,6 +512,7 @@ impl Project { Ok(()) } + /// Flattens the target file into a single string suitable for verification pub fn flatten(&self, target: &Path) -> Result { self.paths.flatten(target) } From 3ece55f21e88895af22e537d83522d3e100c5b0a Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Fri, 14 Jan 2022 18:25:27 +0100 Subject: [PATCH 12/21] add support for ver pragma loc --- Cargo.lock | 2 +- ethers-solc/src/config.rs | 26 ++++++++++++++++++-------- ethers-solc/src/resolver.rs | 28 +++++++++++++++++++++++----- ethers-solc/tests/project.rs | 22 +++++++++++----------- 4 files changed, 53 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e4a572eb..6aae3e17d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3369,7 +3369,7 @@ dependencies = [ [[package]] name = "solang-parser" version = "0.1.1" -source = "git+https://github.com/hyperledger-labs/solang#190f7205dd2ec50f8d930741785a79e6ddf0148b" +source = "git+https://github.com/hyperledger-labs/solang#747e524177a67edea52e2b96eb7acea1faee70e8" dependencies = [ "lalrpop", "lalrpop-util", diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs index 271612bd1..594c7769d 100644 --- a/ethers-solc/src/config.rs +++ b/ethers-solc/src/config.rs @@ -176,10 +176,16 @@ impl ProjectPathsConfig { pub fn flatten(&self, target: &Path) -> Result { tracing::trace!("flattening file"); let graph = Graph::resolve(self)?; - self.flatten_node(target, &graph) + self.flatten_node(target, &graph, false) } - fn flatten_node(&self, target: &Path, graph: &Graph) -> Result { + /// Flattens a single node from the dependency graph + fn flatten_node( + &self, + target: &Path, + graph: &Graph, + omit_version_pragma: bool, + ) -> Result { let target_dir = target.parent().ok_or_else(|| { SolcError::msg(format!("failed to get parent directory for \"{:?}\"", target.display())) })?; @@ -194,14 +200,18 @@ impl ProjectPathsConfig { let mut content = target_node.content().bytes().collect::>(); let mut offset = 0_isize; + if omit_version_pragma { + if let Some(version) = target_node.version() { + let (start, end) = version.loc(); + content.splice(start..end, std::iter::empty()); + offset -= (end - start) as isize; + } + } + for import in imports.iter() { let import_path = self.resolve_import(target_dir, import.path())?; - let import_content = self.flatten_node(&import_path, graph)?; - let import_content = utils::RE_SOL_PRAGMA_VERSION - .replace_all(&import_content, "") - .trim() - .as_bytes() - .to_owned(); + let import_content = self.flatten_node(&import_path, graph, true)?; + let import_content = import_content.trim().as_bytes().to_owned(); let import_content_len = import_content.len() as isize; let (start, end) = import.loc_by_offset(offset).expect("failed to determine import location"); diff --git a/ethers-solc/src/resolver.rs b/ethers-solc/src/resolver.rs index 02525c161..901bc4c39 100644 --- a/ethers-solc/src/resolver.rs +++ b/ethers-solc/src/resolver.rs @@ -405,16 +405,26 @@ impl Node { pub fn imports(&self) -> &Vec { &self.data.imports } + + pub fn version(&self) -> &Option { + &self.data.version + } } #[derive(Debug, Clone)] #[allow(unused)] struct SolData { - version: Option, + version: Option, version_req: Option, imports: Vec, } +#[derive(Debug, Clone)] +pub struct SolVersionPragma { + pub version: String, + loc: Location, +} + #[derive(Debug, Clone)] pub struct SolImport { path: PathBuf, @@ -427,6 +437,12 @@ pub struct Location { pub end: usize, } +impl SolVersionPragma { + pub fn loc(&self) -> (usize, usize) { + (self.loc.start, self.loc.end) + } +} + impl SolImport { pub fn path(&self) -> &PathBuf { &self.path @@ -475,10 +491,11 @@ fn parse_data(content: &str) -> SolData { Ok(units) => { for unit in units.0 { match unit { - SourceUnitPart::PragmaDirective(_, pragma, value) => { + SourceUnitPart::PragmaDirective(loc, _, pragma, value) => { if pragma.name == "solidity" { // we're only interested in the solidity version pragma - version = Some(value.string); + version = + Some(SolVersionPragma { version: value.string, loc: loc.into() }); } } SourceUnitPart::ImportDirective(_, import) => { @@ -501,13 +518,14 @@ fn parse_data(content: &str) -> SolData { "failed to parse solidity ast: \"{:?}\". Falling back to regex to extract data", err ); - version = utils::find_version_pragma(content).map(|m| m.as_str().to_owned()); + version = utils::find_version_pragma(content) + .map(|m| SolVersionPragma { version: m.as_str().to_owned(), loc: m.into() }); imports = utils::find_import_paths(content) .map(|m| SolImport { path: PathBuf::from(m.as_str()), loc: m.into() }) .collect(); } }; - let version_req = version.as_ref().and_then(|v| Solc::version_req(v).ok()); + let version_req = version.as_ref().and_then(|v| Solc::version_req(&v.version).ok()); SolData { version_req, version, imports } } diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs index fee62256e..bba6df60a 100644 --- a/ethers-solc/tests/project.rs +++ b/ethers-solc/tests/project.rs @@ -321,14 +321,14 @@ fn can_flatten_file() { assert!(project.flatten(&target).is_ok()); } -#[test] -fn can_flatten_file_with_external_lib() { - let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/hardhat-sample"); - let target = root.join("contracts").join("Greeter.sol"); - let paths = ProjectPathsConfig::builder() - .sources(root.join("contracts")) - .lib(root.join("node_modules")); - let project = TempProject::::new(paths).unwrap(); - - assert!(project.flatten(&target).is_ok()); -} +// #[test] +// fn can_flatten_file_with_external_lib() { +// let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/hardhat-sample"); +// let target = root.join("contracts").join("Greeter.sol"); +// let paths = ProjectPathsConfig::builder() +// .sources(root.join("contracts")) +// .lib(root.join("node_modules")); +// let project = TempProject::::new(paths).unwrap(); + +// assert!(project.flatten(&target).is_ok()); +// } From 4a4a448d9864fef9c861a0a1b19b5bcd4c110382 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Sat, 15 Jan 2022 14:40:10 +0100 Subject: [PATCH 13/21] address pr comments --- ethers-solc/src/config.rs | 10 ++++------ ethers-solc/src/resolver.rs | 12 +++++------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs index 594c7769d..7336ad158 100644 --- a/ethers-solc/src/config.rs +++ b/ethers-solc/src/config.rs @@ -195,9 +195,9 @@ impl ProjectPathsConfig { let target_node = graph.node(*target_index); let mut imports = target_node.imports().clone(); - imports.sort_by(|a, b| a.loc().0.cmp(&b.loc().0)); + imports.sort_by_key(|x| x.loc().0); - let mut content = target_node.content().bytes().collect::>(); + let mut content = target_node.content().as_bytes().to_vec(); let mut offset = 0_isize; if omit_version_pragma { @@ -213,11 +213,9 @@ impl ProjectPathsConfig { let import_content = self.flatten_node(&import_path, graph, true)?; let import_content = import_content.trim().as_bytes().to_owned(); let import_content_len = import_content.len() as isize; - let (start, end) = - import.loc_by_offset(offset).expect("failed to determine import location"); + let (start, end) = import.loc_by_offset(offset); content.splice(start..end, import_content); - let import_offset = import_content_len - ((end - start) as isize); - offset += import_offset; + offset += import_content_len - ((end - start) as isize); } let result = String::from_utf8(content).map_err(|err| { diff --git a/ethers-solc/src/resolver.rs b/ethers-solc/src/resolver.rs index 901bc4c39..b4f2adc6b 100644 --- a/ethers-solc/src/resolver.rs +++ b/ethers-solc/src/resolver.rs @@ -28,7 +28,6 @@ use std::{ collections::{HashMap, VecDeque}, - convert::TryInto, path::{Path, PathBuf}, }; @@ -452,12 +451,11 @@ impl SolImport { (self.loc.start, self.loc.end) } - pub fn loc_by_offset( - &self, - offset: isize, - ) -> std::result::Result<(usize, usize), std::num::TryFromIntError> { - let (start, end) = (self.loc.start as isize + offset, self.loc.end as isize + offset); - Ok((start.try_into()?, end.try_into()?)) + pub fn loc_by_offset(&self, offset: isize) -> (usize, usize) { + ( + offset.saturating_add(self.loc.start as isize) as usize, + offset.saturating_add(self.loc.end as isize) as usize, + ) } } From f5c5cb87b286a652adb55702c8fd620e174d2c69 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Sat, 15 Jan 2022 14:42:48 +0100 Subject: [PATCH 14/21] uncomment the test --- ethers-solc/tests/project.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs index bba6df60a..fee62256e 100644 --- a/ethers-solc/tests/project.rs +++ b/ethers-solc/tests/project.rs @@ -321,14 +321,14 @@ fn can_flatten_file() { assert!(project.flatten(&target).is_ok()); } -// #[test] -// fn can_flatten_file_with_external_lib() { -// let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/hardhat-sample"); -// let target = root.join("contracts").join("Greeter.sol"); -// let paths = ProjectPathsConfig::builder() -// .sources(root.join("contracts")) -// .lib(root.join("node_modules")); -// let project = TempProject::::new(paths).unwrap(); - -// assert!(project.flatten(&target).is_ok()); -// } +#[test] +fn can_flatten_file_with_external_lib() { + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/hardhat-sample"); + let target = root.join("contracts").join("Greeter.sol"); + let paths = ProjectPathsConfig::builder() + .sources(root.join("contracts")) + .lib(root.join("node_modules")); + let project = TempProject::::new(paths).unwrap(); + + assert!(project.flatten(&target).is_ok()); +} From bd1053c6109cecbad27841faeee4b5274f0cc06d Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Sat, 15 Jan 2022 15:03:46 +0100 Subject: [PATCH 15/21] improve test cov --- ethers-solc/tests/project.rs | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs index fee62256e..2a9a91c83 100644 --- a/ethers-solc/tests/project.rs +++ b/ethers-solc/tests/project.rs @@ -318,17 +318,45 @@ fn can_flatten_file() { .lib(root.join("lib2")); let project = TempProject::::new(paths).unwrap(); - assert!(project.flatten(&target).is_ok()); + let result = project.flatten(&target); + assert!(result.is_ok()); + + let result = result.unwrap(); + assert!(result.find("contract Foo").is_some()); + assert!(result.find("contract Bar").is_some()); } #[test] fn can_flatten_file_with_external_lib() { let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/hardhat-sample"); - let target = root.join("contracts").join("Greeter.sol"); let paths = ProjectPathsConfig::builder() .sources(root.join("contracts")) .lib(root.join("node_modules")); let project = TempProject::::new(paths).unwrap(); - assert!(project.flatten(&target).is_ok()); + let target = root.join("contracts").join("Greeter.sol"); + + let result = project.flatten(&target); + assert!(result.is_ok()); + + let result = result.unwrap(); + assert!(result.find("library console").is_some()); + assert!(result.find("contract Greeter").is_some()); +} + +#[test] +fn can_flatten_file_in_dapp_sample() { + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/dapp-sample"); + let paths = ProjectPathsConfig::builder().sources(root.join("src")).lib(root.join("lib")); + let project = TempProject::::new(paths).unwrap(); + + let target = root.join("src/Dapp.t.sol"); + + let result = project.flatten(&target); + assert!(result.is_ok()); + + let result = result.unwrap(); + assert!(result.find("contract DSTest").is_some()); + assert!(result.find("contract Dapp").is_some()); + assert!(result.find("contract DappTest").is_some()); } From 428016ea67eb449b9a9e0819e1d40bd422811a9a Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Sun, 16 Jan 2022 13:37:53 +0100 Subject: [PATCH 16/21] add handling of sdpx license identifiers --- ethers-solc/src/config.rs | 17 +++++-- ethers-solc/src/resolver.rs | 90 ++++++++++++++++++++++--------------- ethers-solc/src/utils.rs | 3 ++ 3 files changed, 70 insertions(+), 40 deletions(-) diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs index 7336ad158..a6aaa7d28 100644 --- a/ethers-solc/src/config.rs +++ b/ethers-solc/src/config.rs @@ -176,7 +176,7 @@ impl ProjectPathsConfig { pub fn flatten(&self, target: &Path) -> Result { tracing::trace!("flattening file"); let graph = Graph::resolve(self)?; - self.flatten_node(target, &graph, false) + self.flatten_node(target, &graph, false, false) } /// Flattens a single node from the dependency graph @@ -185,6 +185,7 @@ impl ProjectPathsConfig { target: &Path, graph: &Graph, omit_version_pragma: bool, + strip_license: bool, ) -> Result { let target_dir = target.parent().ok_or_else(|| { SolcError::msg(format!("failed to get parent directory for \"{:?}\"", target.display())) @@ -200,17 +201,25 @@ impl ProjectPathsConfig { let mut content = target_node.content().as_bytes().to_vec(); let mut offset = 0_isize; + if strip_license { + if let Some(license) = target_node.license() { + let (start, end) = license.loc_by_offset(offset); + content.splice(start..end, std::iter::empty()); + offset -= (end - start) as isize; + } + } + if omit_version_pragma { if let Some(version) = target_node.version() { - let (start, end) = version.loc(); + let (start, end) = version.loc_by_offset(offset); content.splice(start..end, std::iter::empty()); offset -= (end - start) as isize; } } for import in imports.iter() { - let import_path = self.resolve_import(target_dir, import.path())?; - let import_content = self.flatten_node(&import_path, graph, true)?; + let import_path = self.resolve_import(target_dir, import.data())?; + let import_content = self.flatten_node(&import_path, graph, true, true)?; let import_content = import_content.trim().as_bytes().to_owned(); let import_content_len = import_content.len() as isize; let (start, end) = import.loc_by_offset(offset); diff --git a/ethers-solc/src/resolver.rs b/ethers-solc/src/resolver.rs index b4f2adc6b..9c711d660 100644 --- a/ethers-solc/src/resolver.rs +++ b/ethers-solc/src/resolver.rs @@ -139,7 +139,7 @@ impl Graph { }; for import in node.data.imports.iter() { - match paths.resolve_import(cwd, import.path()) { + match paths.resolve_import(cwd, import.data()) { Ok(import) => { add_node(&mut unresolved, &mut index, &mut resolved_imports, import)?; } @@ -401,50 +401,46 @@ impl Node { &self.source.content } - pub fn imports(&self) -> &Vec { + pub fn imports(&self) -> &Vec> { &self.data.imports } - pub fn version(&self) -> &Option { + pub fn version(&self) -> &Option> { &self.data.version } + + pub fn license(&self) -> &Option> { + &self.data.license + } } #[derive(Debug, Clone)] #[allow(unused)] struct SolData { - version: Option, + license: Option>, + version: Option>, + imports: Vec>, version_req: Option, - imports: Vec, } #[derive(Debug, Clone)] -pub struct SolVersionPragma { - pub version: String, - loc: Location, -} - -#[derive(Debug, Clone)] -pub struct SolImport { - path: PathBuf, +pub struct SolDataUnit { loc: Location, + data: T, } - #[derive(Debug, Clone)] pub struct Location { pub start: usize, pub end: usize, } -impl SolVersionPragma { - pub fn loc(&self) -> (usize, usize) { - (self.loc.start, self.loc.end) +impl SolDataUnit { + pub fn new(data: T, loc: Location) -> Self { + Self { data, loc } } -} -impl SolImport { - pub fn path(&self) -> &PathBuf { - &self.path + pub fn data(&self) -> &T { + &self.data } pub fn loc(&self) -> (usize, usize) { @@ -484,7 +480,7 @@ fn read_node(file: impl AsRef) -> Result { /// parsing fails, we'll fall back to extract that info via regex fn parse_data(content: &str) -> SolData { let mut version = None; - let mut imports = Vec::::new(); + let mut imports = Vec::>::new(); match solang_parser::parse(content, 0) { Ok(units) => { for unit in units.0 { @@ -492,8 +488,7 @@ fn parse_data(content: &str) -> SolData { SourceUnitPart::PragmaDirective(loc, _, pragma, value) => { if pragma.name == "solidity" { // we're only interested in the solidity version pragma - version = - Some(SolVersionPragma { version: value.string, loc: loc.into() }); + version = Some(SolDataUnit::new(value.string, loc.into())); } } SourceUnitPart::ImportDirective(_, import) => { @@ -502,10 +497,7 @@ fn parse_data(content: &str) -> SolData { Import::GlobalSymbol(s, _, l) => (s, l), Import::Rename(s, _, l) => (s, l), }; - imports.push(SolImport { - path: PathBuf::from(import.string), - loc: loc.into(), - }); + imports.push(SolDataUnit::new(PathBuf::from(import.string), loc.into())); } _ => {} } @@ -516,15 +508,41 @@ fn parse_data(content: &str) -> SolData { "failed to parse solidity ast: \"{:?}\". Falling back to regex to extract data", err ); - version = utils::find_version_pragma(content) - .map(|m| SolVersionPragma { version: m.as_str().to_owned(), loc: m.into() }); - imports = utils::find_import_paths(content) - .map(|m| SolImport { path: PathBuf::from(m.as_str()), loc: m.into() }) - .collect(); + version = + capture_outer_and_inner(content, &utils::RE_SOL_PRAGMA_VERSION, &vec!["version"]) + .first() + .map(|(cap, name)| { + SolDataUnit::new(name.as_str().to_owned(), cap.to_owned().into()) + }); + imports = + capture_outer_and_inner(content, &utils::RE_SOL_IMPORT, &vec!["p1", "p2", "p3"]) + .iter() + .map(|(cap, m)| { + SolDataUnit::new(PathBuf::from(m.as_str()), cap.to_owned().into()) + }) + .collect(); } }; - let version_req = version.as_ref().and_then(|v| Solc::version_req(&v.version).ok()); - SolData { version_req, version, imports } + let license = + capture_outer_and_inner(content, &utils::RE_SOL_SDPX_LICENSE_IDENTIFIER, &vec!["license"]) + .first() + .map(|(cap, l)| SolDataUnit::new(l.as_str().to_owned(), cap.to_owned().into())); + let version_req = version.as_ref().and_then(|v| Solc::version_req(v.data()).ok()); + SolData { version_req, version, imports, license } +} + +fn capture_outer_and_inner<'a>( + content: &'a str, + regex: ®ex::Regex, + names: &[&str], +) -> Vec<(regex::Match<'a>, regex::Match<'a>)> { + regex + .captures_iter(content) + .filter_map(|cap| { + let cap_match = names.iter().find_map(|name| cap.name(name)); + cap_match.and_then(|m| cap.get(0).map(|outer| (outer.to_owned(), m))) + }) + .collect() } #[cfg(test)] @@ -571,7 +589,7 @@ mod tests { let dapp_test = graph.node(1); assert_eq!(dapp_test.path, paths.sources.join("Dapp.t.sol")); assert_eq!( - dapp_test.data.imports.iter().map(|i| i.path()).collect::>(), + dapp_test.data.imports.iter().map(|i| i.data()).collect::>(), vec![&PathBuf::from("ds-test/test.sol"), &PathBuf::from("./Dapp.sol")] ); assert_eq!(graph.imported_nodes(1).to_vec(), vec![2, 0]); diff --git a/ethers-solc/src/utils.rs b/ethers-solc/src/utils.rs index 8d48fcbc4..307fc7863 100644 --- a/ethers-solc/src/utils.rs +++ b/ethers-solc/src/utils.rs @@ -26,6 +26,9 @@ pub static RE_SOL_IMPORT: Lazy = Lazy::new(|| { pub static RE_SOL_PRAGMA_VERSION: Lazy = Lazy::new(|| Regex::new(r"pragma\s+solidity\s+(?P.+?);").unwrap()); +pub static RE_SOL_SDPX_LICENSE_IDENTIFIER: Lazy = + Lazy::new(|| Regex::new(r"///?\s*SPDX-License-Identifier:\s*(?P.+)").unwrap()); + /// Returns all path parts from any solidity import statement in a string, /// `import "./contracts/Contract.sol";` -> `"./contracts/Contract.sol"`. /// From e1a4ff039a189b516b41dc34c3884c7c61114408 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Sun, 16 Jan 2022 13:44:55 +0100 Subject: [PATCH 17/21] change arg name --- ethers-solc/src/config.rs | 4 ++-- ethers-solc/src/resolver.rs | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs index a6aaa7d28..8ed922578 100644 --- a/ethers-solc/src/config.rs +++ b/ethers-solc/src/config.rs @@ -184,7 +184,7 @@ impl ProjectPathsConfig { &self, target: &Path, graph: &Graph, - omit_version_pragma: bool, + strip_version_pragma: bool, strip_license: bool, ) -> Result { let target_dir = target.parent().ok_or_else(|| { @@ -209,7 +209,7 @@ impl ProjectPathsConfig { } } - if omit_version_pragma { + if strip_version_pragma { if let Some(version) = target_node.version() { let (start, end) = version.loc_by_offset(offset); content.splice(start..end, std::iter::empty()); diff --git a/ethers-solc/src/resolver.rs b/ethers-solc/src/resolver.rs index 1615cff9b..9c711d660 100644 --- a/ethers-solc/src/resolver.rs +++ b/ethers-solc/src/resolver.rs @@ -485,11 +485,7 @@ fn parse_data(content: &str) -> SolData { Ok(units) => { for unit in units.0 { match unit { -<<<<<<< HEAD SourceUnitPart::PragmaDirective(loc, _, pragma, value) => { -======= - SourceUnitPart::PragmaDirective(_, _, pragma, value) => { ->>>>>>> master if pragma.name == "solidity" { // we're only interested in the solidity version pragma version = Some(SolDataUnit::new(value.string, loc.into())); From 41f256dd3987ce934e166c2717817f45c3d2565b Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Sun, 16 Jan 2022 14:25:55 +0100 Subject: [PATCH 18/21] match license only at the beginning of the file --- ethers-solc/src/resolver.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ethers-solc/src/resolver.rs b/ethers-solc/src/resolver.rs index 9c711d660..51d224a08 100644 --- a/ethers-solc/src/resolver.rs +++ b/ethers-solc/src/resolver.rs @@ -523,10 +523,11 @@ fn parse_data(content: &str) -> SolData { .collect(); } }; - let license = - capture_outer_and_inner(content, &utils::RE_SOL_SDPX_LICENSE_IDENTIFIER, &vec!["license"]) + let license = content.lines().nth(0).and_then(|line| { + capture_outer_and_inner(line, &utils::RE_SOL_SDPX_LICENSE_IDENTIFIER, &vec!["license"]) .first() - .map(|(cap, l)| SolDataUnit::new(l.as_str().to_owned(), cap.to_owned().into())); + .map(|(cap, l)| SolDataUnit::new(l.as_str().to_owned(), cap.to_owned().into())) + }); let version_req = version.as_ref().and_then(|v| Solc::version_req(v.data()).ok()); SolData { version_req, version, imports, license } } From 0ff30c0a73c345259d82a15adb60703159060f0a Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Sun, 16 Jan 2022 15:29:47 +0100 Subject: [PATCH 19/21] add comments --- ethers-solc/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs index 117a283cd..44e00bda2 100644 --- a/ethers-solc/src/lib.rs +++ b/ethers-solc/src/lib.rs @@ -513,6 +513,12 @@ impl Project { } /// Flattens the target file into a single string suitable for verification + /// + /// This method uses a dependency graph to resolve imported files and substitute + /// import directives with the contents of target files. It will strip the pragma + /// version directives and SDPX license identifiers from imported files. + /// Caution: the SDPX license identifier will be removed from the imported file + /// only if it is at the beginning of the file. pub fn flatten(&self, target: &Path) -> Result { self.paths.flatten(target) } From e08d84c18ae69e764038a6d57e7d0479970c325c Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Sun, 16 Jan 2022 15:31:29 +0100 Subject: [PATCH 20/21] lint --- ethers-solc/src/resolver.rs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/ethers-solc/src/resolver.rs b/ethers-solc/src/resolver.rs index 51d224a08..60bcaa84a 100644 --- a/ethers-solc/src/resolver.rs +++ b/ethers-solc/src/resolver.rs @@ -508,23 +508,19 @@ fn parse_data(content: &str) -> SolData { "failed to parse solidity ast: \"{:?}\". Falling back to regex to extract data", err ); - version = - capture_outer_and_inner(content, &utils::RE_SOL_PRAGMA_VERSION, &vec!["version"]) - .first() - .map(|(cap, name)| { - SolDataUnit::new(name.as_str().to_owned(), cap.to_owned().into()) - }); - imports = - capture_outer_and_inner(content, &utils::RE_SOL_IMPORT, &vec!["p1", "p2", "p3"]) - .iter() - .map(|(cap, m)| { - SolDataUnit::new(PathBuf::from(m.as_str()), cap.to_owned().into()) - }) - .collect(); + version = capture_outer_and_inner(content, &utils::RE_SOL_PRAGMA_VERSION, &["version"]) + .first() + .map(|(cap, name)| { + SolDataUnit::new(name.as_str().to_owned(), cap.to_owned().into()) + }); + imports = capture_outer_and_inner(content, &utils::RE_SOL_IMPORT, &["p1", "p2", "p3"]) + .iter() + .map(|(cap, m)| SolDataUnit::new(PathBuf::from(m.as_str()), cap.to_owned().into())) + .collect(); } }; - let license = content.lines().nth(0).and_then(|line| { - capture_outer_and_inner(line, &utils::RE_SOL_SDPX_LICENSE_IDENTIFIER, &vec!["license"]) + let license = content.lines().next().and_then(|line| { + capture_outer_and_inner(line, &utils::RE_SOL_SDPX_LICENSE_IDENTIFIER, &["license"]) .first() .map(|(cap, l)| SolDataUnit::new(l.as_str().to_owned(), cap.to_owned().into())) }); From e43c3b7de405d5062837513fe1b6749ebbb303ed Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Sun, 16 Jan 2022 15:45:08 +0100 Subject: [PATCH 21/21] morrre comments --- ethers-solc/src/lib.rs | 5 +++-- ethers-solc/src/resolver.rs | 13 +++++++++++++ ethers-solc/src/utils.rs | 6 ++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs index 44e00bda2..d68e7cd80 100644 --- a/ethers-solc/src/lib.rs +++ b/ethers-solc/src/lib.rs @@ -517,8 +517,9 @@ impl Project { /// This method uses a dependency graph to resolve imported files and substitute /// import directives with the contents of target files. It will strip the pragma /// version directives and SDPX license identifiers from imported files. - /// Caution: the SDPX license identifier will be removed from the imported file - /// only if it is at the beginning of the file. + /// + /// NOTE: the SDPX license identifier will be removed from the imported file + /// only if it is found at the beginning of the file. pub fn flatten(&self, target: &Path) -> Result { self.paths.flatten(target) } diff --git a/ethers-solc/src/resolver.rs b/ethers-solc/src/resolver.rs index 60bcaa84a..be95f68d8 100644 --- a/ethers-solc/src/resolver.rs +++ b/ethers-solc/src/resolver.rs @@ -434,19 +434,25 @@ pub struct Location { pub end: usize, } +/// Solidity Data Unit decorated with its location within the file impl SolDataUnit { pub fn new(data: T, loc: Location) -> Self { Self { data, loc } } + /// Returns the underlying data for the unit pub fn data(&self) -> &T { &self.data } + /// Returns the location of the given data unit pub fn loc(&self) -> (usize, usize) { (self.loc.start, self.loc.end) } + /// Returns the location of the given data unit adjusted by an offset. + /// Used to determine new position of the unit within the file after + /// content manipulation. pub fn loc_by_offset(&self, offset: isize) -> (usize, usize) { ( offset.saturating_add(self.loc.start as isize) as usize, @@ -528,6 +534,13 @@ fn parse_data(content: &str) -> SolData { SolData { version_req, version, imports, license } } +/// Given the regex and the target string, find all occurrences +/// of named groups within the string. This method returns +/// the tuple of matches `(a, b)` where `a` is the match for the +/// entire regex and `b` is the match for the first named group. +/// +/// NOTE: This method will return the match for the first named +/// group, so the order of passed named groups matters. fn capture_outer_and_inner<'a>( content: &'a str, regex: ®ex::Regex, diff --git a/ethers-solc/src/utils.rs b/ethers-solc/src/utils.rs index 307fc7863..fc1960b44 100644 --- a/ethers-solc/src/utils.rs +++ b/ethers-solc/src/utils.rs @@ -14,18 +14,20 @@ use walkdir::WalkDir; /// A regex that matches the import path and identifier of a solidity import /// statement with the named groups "path", "id". +// Adapted from https://github.com/nomiclabs/hardhat/blob/cced766c65b25d3d0beb39ef847246ac9618bdd9/packages/hardhat-core/src/internal/solidity/parse.ts#L100 pub static RE_SOL_IMPORT: Lazy = Lazy::new(|| { - // Adapted from https://github.com/nomiclabs/hardhat/blob/cced766c65b25d3d0beb39ef847246ac9618bdd9/packages/hardhat-core/src/internal/solidity/parse.ts#L100 Regex::new(r#"import\s+(?:(?:"(?P[^;]*)"|'([^;]*)')(?:;|\s+as\s+(?P[^;]*);)|.+from\s+(?:"(?P.*)"|'(?P.*)');)"#).unwrap() }); /// A regex that matches the version part of a solidity pragma /// as follows: `pragma solidity ^0.5.2;` => `^0.5.2` -/// statement with the named groups "path", "id". +/// statement with the named group "version". // Adapted from https://github.com/nomiclabs/hardhat/blob/cced766c65b25d3d0beb39ef847246ac9618bdd9/packages/hardhat-core/src/internal/solidity/parse.ts#L119 pub static RE_SOL_PRAGMA_VERSION: Lazy = Lazy::new(|| Regex::new(r"pragma\s+solidity\s+(?P.+?);").unwrap()); +/// A regex that matches the SDPX license identifier +/// statement with the named group "license". pub static RE_SOL_SDPX_LICENSE_IDENTIFIER: Lazy = Lazy::new(|| Regex::new(r"///?\s*SPDX-License-Identifier:\s*(?P.+)").unwrap());