Repository: gakonst/ethers-rs
Pull request #1149 (2022-04-16 07:51:16)

fix(solc): flatten consistent spacing
<!--
Thank you for your Pull Request. Please provide a description above and review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide: https://github.com/gakonst/ethers-rs/blob/master/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and building the
documentation.
-->

## Motivation

[Foundry issue #796](https://github.com/foundry-rs/foundry/issues/796)

<!--
Explain the context and why you're making that change. What is the problem
you're trying to solve? In some cases there is not a problem and this can be
thought of as being the motivation for your change.
-->

## Solution

Introduce consistent spacing on the final flattened string.

<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

- [x] Added Tests
- [x] Added Documentation
- [ ] Updated the changelog

CC: @mattsse @onbjerg 


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index dac6a7a5..9347f3ae 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3585,7 +3585,7 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
 [[package]]
 name = "svm-rs"
 version = "0.2.9"
-source = "git+https://github.com/roynalnaruto/svm-rs#9c86111689147842dd76f5fe8db7cf472e729118"
+source = "git+https://github.com/roynalnaruto/svm-rs#1eb7f4e2ccb549096885ded598f6c05b2d96dccf"
 dependencies = [
  "anyhow",
  "cfg-if 1.0.0",
@@ -3614,7 +3614,7 @@ dependencies = [
 [[package]]
 name = "svm-rs-builds"
 version = "0.1.0"
-source = "git+https://github.com/roynalnaruto/svm-rs#9c86111689147842dd76f5fe8db7cf472e729118"
+source = "git+https://github.com/roynalnaruto/svm-rs#1eb7f4e2ccb549096885ded598f6c05b2d96dccf"
 dependencies = [
  "build_const",
  "hex",
diff --git a/ethers-contract/ethers-contract-derive/src/display.rs b/ethers-contract/ethers-contract-derive/src/display.rs
index 45918207..8fc613f1 100644
--- a/ethers-contract/ethers-contract-derive/src/display.rs
+++ b/ethers-contract/ethers-contract-derive/src/display.rs
@@ -42,7 +42,7 @@ pub(crate) fn derive_eth_display_impl(input: DeriveInput) -> Result<TokenStream,
                 }
                 ParamType::Bytes => {
                     quote! {
-                         write!(f, "0x{}", #hex_encode(self.#ident))?;
+                         write!(f, "0x{}", #hex_encode(&self.#ident))?;
                     }
                 }
                 ParamType::Bool | ParamType::String => {
diff --git a/ethers-contract/ethers-contract-derive/src/utils.rs b/ethers-contract/ethers-contract-derive/src/utils.rs
index 066cbbfa..abef0999 100644
--- a/ethers-contract/ethers-contract-derive/src/utils.rs
+++ b/ethers-contract/ethers-contract-derive/src/utils.rs
@@ -111,10 +111,26 @@ pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
             Err(Error::new(ty.span(), "Failed to derive proper ABI from array field"))
         }
         Type::Path(ty) => {
-            if let Some(ident) = ty.path.get_ident() {
+            // check for `Vec`
+            if ty.path.segments.len() == 1 && ty.path.segments[0].ident == "Vec" {
+                if let PathArguments::AngleBracketed(ref args) = ty.path.segments[0].arguments {
+                    if args.args.len() == 1 {
+                        if let GenericArgument::Type(ref ty) = args.args.iter().next().unwrap() {
+                            let kind = find_parameter_type(ty)?;
+                            return Ok(ParamType::Array(Box::new(kind)))
+                        }
+                    }
+                }
+            }
+            let mut ident = ty.path.get_ident();
+            if ident.is_none() {
+                ident = ty.path.segments.last().map(|s| &s.ident);
+            }
+            if let Some(ident) = ident {
                 let ident = ident.to_string().to_lowercase();
                 return match ident.as_str() {
                     "address" => Ok(ParamType::Address),
+                    "bytes" => Ok(ParamType::Bytes),
                     "string" => Ok(ParamType::String),
                     "bool" => Ok(ParamType::Bool),
                     "int" | "uint" => Ok(ParamType::Uint(256)),
@@ -126,18 +142,6 @@ pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
                     }),
                 }
             }
-            // check for `Vec`
-            if ty.path.segments.len() == 1 && ty.path.segments[0].ident == "Vec" {
-                if let PathArguments::AngleBracketed(ref args) = ty.path.segments[0].arguments {
-                    if args.args.len() == 1 {
-                        if let GenericArgument::Type(ref ty) = args.args.iter().next().unwrap() {
-                            let kind = find_parameter_type(ty)?;
-                            return Ok(ParamType::Array(Box::new(kind)))
-                        }
-                    }
-                }
-            }
-
             Err(Error::new(ty.span(), "Failed to derive proper ABI from fields"))
         }
         Type::Tuple(ty) => {
diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs
index d39db454..3b67de83 100644
--- a/ethers-solc/src/config.rs
+++ b/ethers-solc/src/config.rs
@@ -311,7 +311,7 @@ impl ProjectPathsConfig {
         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, true)?;
-            let import_content = s.trim().as_bytes();
+            let import_content = s.as_bytes();
             let import_content_len = import_content.len() as isize;
             let (start, end) = import.loc_by_offset(offset);
             content.splice(start..end, import_content.iter().copied());
@@ -321,6 +321,7 @@ impl ProjectPathsConfig {
         let result = String::from_utf8(content).map_err(|err| {
             SolcError::msg(format!("failed to convert extended bytes to string: {}", err))
         })?;
+        let result = utils::RE_THREE_OR_MORE_NEWLINES.replace_all(&result, "\n\n").into_owned();
 
         Ok(result)
     }
diff --git a/ethers-solc/src/utils.rs b/ethers-solc/src/utils.rs
index cbc42d51..bd354228 100644
--- a/ethers-solc/src/utils.rs
+++ b/ethers-solc/src/utils.rs
@@ -32,6 +32,9 @@ pub static RE_SOL_PRAGMA_VERSION: Lazy<Regex> =
 pub static RE_SOL_SDPX_LICENSE_IDENTIFIER: Lazy<Regex> =
     Lazy::new(|| Regex::new(r"///?\s*SPDX-License-Identifier:\s*(?P<license>.+)").unwrap());
 
+/// A regex used to remove extra lines in flatenned files
+pub static RE_THREE_OR_MORE_NEWLINES: Lazy<Regex> = Lazy::new(|| Regex::new("\n{3,}").unwrap());
+
 /// Returns all path parts from any solidity import statement in a string,
 /// `import "./contracts/Contract.sol";` -> `"./contracts/Contract.sol"`.
 ///


Test patch:
diff --git a/ethers-contract/tests/common/derive.rs b/ethers-contract/tests/common/derive.rs
index 65f07ca7..25dd7aaf 100644
--- a/ethers-contract/tests/common/derive.rs
+++ b/ethers-contract/tests/common/derive.rs
@@ -583,3 +583,16 @@ fn can_derive_array_tuples() {
         pub calldata: Bytes,
     }
 }
+
+#[test]
+fn eth_display_works_on_ethers_bytes() {
+    #[derive(Clone, Debug, Default, Eq, PartialEq, EthCall, EthDisplay)]
+    #[ethcall(name = "logBytes", abi = "logBytes(bytes)")]
+    pub struct LogBytesCall {
+        pub p_0: ethers_core::types::Bytes,
+    }
+    let call = LogBytesCall { p_0: hex::decode(b"aaaaaa").unwrap().into() };
+
+    let s = format!("{}", call);
+    assert_eq!(s, "0xaaaaaa");
+}
diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs
index 86939884..2b360c9e 100644
--- a/ethers-solc/tests/project.rs
+++ b/ethers-solc/tests/project.rs
@@ -492,8 +492,11 @@ contract C { }
         result,
         r#"
 pragma solidity ^0.8.10;
+
 contract C { }
+
 contract B { }
+
 contract A { }
 "#
     );
@@ -547,8 +550,11 @@ contract C { }
         r#"
 pragma solidity ^0.8.10;
 pragma experimental ABIEncoderV2;
+
 contract C { }
+
 contract B { }
+
 contract A { }
 "#
     );
@@ -572,6 +578,7 @@ fn can_flatten_file_with_duplicates() {
 pragma solidity >=0.6.0;
 
 contract Bar {}
+
 contract Foo {}
 
 contract FooBar {}
@@ -598,6 +605,7 @@ fn can_flatten_on_solang_failure() {
 pragma solidity ^0.8.10;
 
 library Lib {}
+
 // Intentionally erroneous code
 contract Contract {
     failure();
@@ -650,9 +658,64 @@ contract C { }
     assert_eq!(
         result.trim(),
         r#"pragma solidity ^0.8.10;
+
 contract C { }
+
 error IllegalArgument();
 error IllegalState();
+
+contract A { }"#
+    );
+}
+
+#[test]
+fn can_flatten_remove_extra_spacing() {
+    let project = TempProject::dapptools().unwrap();
+
+    let f = project
+        .add_source(
+            "A",
+            r#"pragma solidity ^0.8.10;
+import "./C.sol";
+import "./B.sol";
+contract A { }
+"#,
+        )
+        .unwrap();
+
+    project
+        .add_source(
+            "B",
+            r#"// This is a B Contract
+pragma solidity ^0.8.10;
+
+import "./C.sol";
+
+contract B { }
+"#,
+        )
+        .unwrap();
+
+    project
+        .add_source(
+            "C",
+            r#"pragma solidity ^0.8.10;
+contract C { }
+"#,
+        )
+        .unwrap();
+
+    let result = project.flatten(&f).unwrap();
+    assert_eq!(
+        result.trim(),
+        r#"pragma solidity ^0.8.10;
+
+contract C { }
+
+// This is a B Contract
+
+contract B { }
+
 contract A { }"#
     );
 }