Repository: gakonst/ethers-rs (Rust)

Issue:
[ethers-solc] `pragma experimental` is not stripped on flatten
**Version**
List the versions of all `ethers-rs` crates you are using. The easiest way to get
this information is using `cargo-tree`.

`cargo install cargo-tree`
(see install here: https://github.com/sfackler/cargo-tree)

Then:

`cargo tree | grep ethers`

```
✦ forge --version
forge 0.2.0 (233ab70 2022-04-01T00:48:24.366564+00:00)
```

I think foundry uses `ethers-rs` on master branch.


**Platform**
The output of `uname -a` (UNIX), or version and 32 or 64-bit (Windows)

```
✦ uname -a
Darwin pyk-iMac.local 21.4.0 Darwin Kernel Version 21.4.0: Mon Feb 21 20:36:53 PST 2022; root:xnu-8020.101.4~2/RELEASE_ARM64_T8101 x86_64
```

**Description**
Enter your issue details here.
One way to structure the description:

`pragma experimental` is not stripped on flatten

I tried this code:

```
forge flatten src/Contract.sol > output.sol
```

I expected to see this happen:

`pragma experimental ABIEncoderV2;` will be stripped on each import.

Instead, this happened: 

`pragma experimental ABIEncoderV2;` is not stripped, this issue cause the verify contract on Forge to failed:

```
Error:
   0: Contract verification failed:
      Response: `NOTOK`
      Details: `Fail - Unable to verify`
```

--

https://github.com/gakonst/foundry/issues/852




Discussion:
thanks for this!
so all `pragma experimental ABIEncoderV2;` should be removed or should only a single one be kept?
@mattsse I think only the main contract should have the experimental pragma. All imported contracts should be stripped.

I make some PR regarding this #1116 

Let me know what u think! 

Thanks 🙇 



Patch:
diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs
index 6ffda54ce..d39db4545 100644
--- a/ethers-solc/src/config.rs
+++ b/ethers-solc/src/config.rs
@@ -250,7 +250,7 @@ impl ProjectPathsConfig {
     pub fn flatten(&self, target: &Path) -> Result<String> {
         tracing::trace!("flattening file");
         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
@@ -260,6 +260,7 @@ impl ProjectPathsConfig {
         graph: &Graph,
         imported: &mut HashSet<usize>,
         strip_version_pragma: bool,
+        strip_experimental_pragma: bool,
         strip_license: bool,
     ) -> Result<String> {
         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() {
             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_len = import_content.len() as isize;
             let (start, end) = import.loc_by_offset(offset);
diff --git a/ethers-solc/src/resolver/mod.rs b/ethers-solc/src/resolver/mod.rs
index bb947ae24..a739db594 100644
--- a/ethers-solc/src/resolver/mod.rs
+++ b/ethers-solc/src/resolver/mod.rs
@@ -763,6 +763,10 @@ impl Node {
         &self.data.version
     }
 
+    pub fn experimental(&self) -> &Option<SolDataUnit<String>> {
+        &self.data.experimental
+    }
+
     pub fn license(&self) -> &Option<SolDataUnit<String>> {
         &self.data.license
     }
diff --git a/ethers-solc/src/resolver/parse.rs b/ethers-solc/src/resolver/parse.rs
index eba8b7395..028856f9d 100644
--- a/ethers-solc/src/resolver/parse.rs
+++ b/ethers-solc/src/resolver/parse.rs
@@ -13,6 +13,7 @@ use std::path::{Path, PathBuf};
 pub struct SolData {
     pub license: Option<SolDataUnit<String>>,
     pub version: Option<SolDataUnit<String>>,
+    pub experimental: Option<SolDataUnit<String>>,
     pub imports: Vec<SolDataUnit<PathBuf>>,
     pub version_req: Option<VersionReq>,
     pub libraries: Vec<SolLibrary>,
@@ -37,6 +38,7 @@ impl SolData {
     /// parsing fails, we'll fall back to extract that info via regex
     pub fn parse(content: &str, file: &Path) -> Self {
         let mut version = None;
+        let mut experimental = None;
         let mut imports = Vec::<SolDataUnit<PathBuf>>::new();
         let mut libraries = Vec::new();
         let mut contracts = Vec::new();
@@ -48,7 +50,12 @@ impl SolData {
                         SourceUnitPart::PragmaDirective(loc, _, pragma, value) => {
                             if pragma.name == "solidity" {
                                 // 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) => {
@@ -106,7 +113,7 @@ impl SolData {
         });
         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


Test patch:
diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs
index 463758c37..fd3498d70 100644
--- a/ethers-solc/tests/project.rs
+++ b/ethers-solc/tests/project.rs
@@ -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]
 fn can_flatten_file_with_duplicates() {
     let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/test-flatten-duplicates");