feat(solc): Strip experimental pragma from all imported contracts (#1116)

* Parse experimental pragma

* add integration test for experimental pragma

* Fix formatting
This commit is contained in:
pyk 2022-04-07 17:12:25 +07:00 committed by GitHub
parent 509db06080
commit 77931d4c63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 79 additions and 4 deletions

View File

@ -250,7 +250,7 @@ impl ProjectPathsConfig {
pub fn flatten(&self, target: &Path) -> Result<String> { pub fn flatten(&self, target: &Path) -> Result<String> {
tracing::trace!("flattening file"); tracing::trace!("flattening file");
let graph = Graph::resolve(self)?; let graph = Graph::resolve(self)?;
self.flatten_node(target, &graph, &mut Default::default(), false, false) self.flatten_node(target, &graph, &mut Default::default(), false, false, false)
} }
/// Flattens a single node from the dependency graph /// Flattens a single node from the dependency graph
@ -260,6 +260,7 @@ impl ProjectPathsConfig {
graph: &Graph, graph: &Graph,
imported: &mut HashSet<usize>, imported: &mut HashSet<usize>,
strip_version_pragma: bool, strip_version_pragma: bool,
strip_experimental_pragma: bool,
strip_license: bool, strip_license: bool,
) -> Result<String> { ) -> Result<String> {
let target_dir = target.parent().ok_or_else(|| { let target_dir = target.parent().ok_or_else(|| {
@ -299,9 +300,17 @@ impl ProjectPathsConfig {
} }
} }
if strip_experimental_pragma {
if let Some(experiment) = target_node.experimental() {
let (start, end) = experiment.loc_by_offset(offset);
content.splice(start..end, std::iter::empty());
offset -= (end - start) as isize;
}
}
for import in imports.iter() { for import in imports.iter() {
let import_path = self.resolve_import(target_dir, import.data())?; let import_path = self.resolve_import(target_dir, import.data())?;
let s = self.flatten_node(&import_path, graph, imported, true, true)?; let s = self.flatten_node(&import_path, graph, imported, true, true, true)?;
let import_content = s.trim().as_bytes(); let import_content = s.trim().as_bytes();
let import_content_len = import_content.len() as isize; let import_content_len = import_content.len() as isize;
let (start, end) = import.loc_by_offset(offset); let (start, end) = import.loc_by_offset(offset);

View File

@ -763,6 +763,10 @@ impl Node {
&self.data.version &self.data.version
} }
pub fn experimental(&self) -> &Option<SolDataUnit<String>> {
&self.data.experimental
}
pub fn license(&self) -> &Option<SolDataUnit<String>> { pub fn license(&self) -> &Option<SolDataUnit<String>> {
&self.data.license &self.data.license
} }

View File

@ -13,6 +13,7 @@ use std::path::{Path, PathBuf};
pub struct SolData { pub struct SolData {
pub license: Option<SolDataUnit<String>>, pub license: Option<SolDataUnit<String>>,
pub version: Option<SolDataUnit<String>>, pub version: Option<SolDataUnit<String>>,
pub experimental: Option<SolDataUnit<String>>,
pub imports: Vec<SolDataUnit<PathBuf>>, pub imports: Vec<SolDataUnit<PathBuf>>,
pub version_req: Option<VersionReq>, pub version_req: Option<VersionReq>,
pub libraries: Vec<SolLibrary>, pub libraries: Vec<SolLibrary>,
@ -37,6 +38,7 @@ impl SolData {
/// parsing fails, we'll fall back to extract that info via regex /// parsing fails, we'll fall back to extract that info via regex
pub fn parse(content: &str, file: &Path) -> Self { pub fn parse(content: &str, file: &Path) -> Self {
let mut version = None; let mut version = None;
let mut experimental = None;
let mut imports = Vec::<SolDataUnit<PathBuf>>::new(); let mut imports = Vec::<SolDataUnit<PathBuf>>::new();
let mut libraries = Vec::new(); let mut libraries = Vec::new();
let mut contracts = Vec::new(); let mut contracts = Vec::new();
@ -48,7 +50,12 @@ impl SolData {
SourceUnitPart::PragmaDirective(loc, _, pragma, value) => { SourceUnitPart::PragmaDirective(loc, _, pragma, value) => {
if pragma.name == "solidity" { if pragma.name == "solidity" {
// we're only interested in the solidity version pragma // we're only interested in the solidity version pragma
version = Some(SolDataUnit::new(value.string, loc.into())); version = Some(SolDataUnit::new(value.string.clone(), loc.into()));
}
if pragma.name == "experimental" {
experimental =
Some(SolDataUnit::new(value.string.clone(), loc.into()));
} }
} }
SourceUnitPart::ImportDirective(_, import) => { SourceUnitPart::ImportDirective(_, import) => {
@ -106,7 +113,7 @@ impl SolData {
}); });
let version_req = version.as_ref().and_then(|v| Solc::version_req(v.data()).ok()); let version_req = version.as_ref().and_then(|v| Solc::version_req(v.data()).ok());
Self { version_req, version, imports, license, libraries, contracts } Self { version_req, version, experimental, imports, license, libraries, contracts }
} }
/// Returns `true` if the solidity file associated with this type contains a solidity library /// Returns `true` if the solidity file associated with this type contains a solidity library

View File

@ -499,6 +499,61 @@ contract A { }
); );
} }
#[test]
fn can_flatten_experimental_pragma() {
let project = TempProject::dapptools().unwrap();
let f = project
.add_source(
"A",
r#"
pragma solidity ^0.8.10;
pragma experimental ABIEncoderV2;
import "./C.sol";
import "./B.sol";
contract A { }
"#,
)
.unwrap();
project
.add_source(
"B",
r#"
pragma solidity ^0.8.10;
pragma experimental ABIEncoderV2;
import "./C.sol";
contract B { }
"#,
)
.unwrap();
project
.add_source(
"C",
r#"
pragma solidity ^0.8.10;
pragma experimental ABIEncoderV2;
import "./A.sol";
contract C { }
"#,
)
.unwrap();
let result = project.flatten(&f).unwrap();
assert_eq!(
result,
r#"
pragma solidity ^0.8.10;
pragma experimental ABIEncoderV2;
contract C { }
contract B { }
contract A { }
"#
);
}
#[test] #[test]
fn can_flatten_file_with_duplicates() { fn can_flatten_file_with_duplicates() {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/test-flatten-duplicates"); let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/test-flatten-duplicates");