Repository: gakonst/ethers-rs (rust)

Issue:
`ethers-solc` Compiler Output Metadata Double Serialized
**Version**
```
forge 0.2.0 (dd7c88f 2022-07-08T00:06:14.769137084Z)
```

**Description**
The metadata in the compiler output has a raw output string that comes from solc. When it is serialized, it ends up getting serialized as a string with escapes instead of a json object. The offending line is here:

https://github.com/gakonst/ethers-rs/blob/7566c3d71e69553b609c1b0b9987a9c7d2df3035/ethers-solc/src/artifacts/mod.rs#L901

I have a failing test case here: https://github.com/foundry-rs/hardhat/pull/85

I believe that `hardhat etherscan-verify` will work based on the build info once this is fixed.


Pull request:
fix(solc): serialize metadata as raw string
<!--
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
Closes #1473
<!--
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
the `raw_metadata` string was serialized incorrectly, as `String::serialize` will escape.

correct way is `serializer.serialize_str()`
<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

- [x] Added Tests
- [ ] Added Documentation
- [ ] Updated the changelog


Patch:
diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs
index c08b64b2..d9005879 100644
--- a/ethers-solc/src/artifacts/mod.rs
+++ b/ethers-solc/src/artifacts/mod.rs
@@ -898,7 +898,7 @@ impl Serialize for LosslessMetadata {
     where
         S: Serializer,
     {
-        self.raw_metadata.serialize(serializer)
+        serializer.serialize_str(&self.raw_metadata)
     }
 }
 
@@ -920,8 +920,8 @@ impl<'de> Deserialize<'de> for LosslessMetadata {
             where
                 E: serde::de::Error,
             {
-                let raw_metadata = value.to_string();
                 let metadata = serde_json::from_str(value).map_err(serde::de::Error::custom)?;
+                let raw_metadata = value.to_string();
                 Ok(LosslessMetadata { raw_metadata, metadata })
             }
         }
@@ -2097,4 +2097,27 @@ mod tests {
             ])
         );
     }
+
+    #[test]
+    fn test_lossless_metadata() {
+        #[derive(Debug, Serialize, Deserialize)]
+        #[serde(rename_all = "camelCase")]
+        pub struct Contract {
+            #[serde(
+                default,
+                skip_serializing_if = "Option::is_none",
+                with = "serde_helpers::json_string_opt"
+            )]
+            pub metadata: Option<LosslessMetadata>,
+        }
+
+        let s = r#"{"metadata":"{\"compiler\":{\"version\":\"0.4.18+commit.9cf6e910\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}],\"devdoc\":{\"methods\":{\"transferOwnership(address)\":{\"details\":\"Allows the current owner to transfer control of the contract to a newOwner.\",\"params\":{\"newOwner\":\"The address to transfer ownership to.\"}}},\"title\":\"Ownable\"},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"src/Contract.sol\":\"Ownable\"},\"libraries\":{},\"optimizer\":{\"enabled\":true,\"runs\":1000000},\"remappings\":[\":src/=src/\"]},\"sources\":{\"src/Contract.sol\":{\"keccak256\":\"0x3e0d611f53491f313ae035797ed7ecfd1dfd8db8fef8f82737e6f0cd86d71de7\",\"urls\":[\"bzzr://9c33025fa9d1b8389e4c7c9534a1d70fad91c6c2ad70eb5e4b7dc3a701a5f892\"]}},\"version\":1}"}"#;
+
+        let value: serde_json::Value = serde_json::from_str(s).unwrap();
+        let c: Contract = serde_json::from_value(value).unwrap();
+        assert_eq!(c.metadata.as_ref().unwrap().raw_metadata, "{\"compiler\":{\"version\":\"0.4.18+commit.9cf6e910\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}],\"devdoc\":{\"methods\":{\"transferOwnership(address)\":{\"details\":\"Allows the current owner to transfer control of the contract to a newOwner.\",\"params\":{\"newOwner\":\"The address to transfer ownership to.\"}}},\"title\":\"Ownable\"},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"src/Contract.sol\":\"Ownable\"},\"libraries\":{},\"optimizer\":{\"enabled\":true,\"runs\":1000000},\"remappings\":[\":src/=src/\"]},\"sources\":{\"src/Contract.sol\":{\"keccak256\":\"0x3e0d611f53491f313ae035797ed7ecfd1dfd8db8fef8f82737e6f0cd86d71de7\",\"urls\":[\"bzzr://9c33025fa9d1b8389e4c7c9534a1d70fad91c6c2ad70eb5e4b7dc3a701a5f892\"]}},\"version\":1}");
+
+        let value = serde_json::to_string(&c).unwrap();
+        pretty_assertions::assert_eq!(s, value);
+    }
 }
diff --git a/ethers-solc/src/artifacts/serde_helpers.rs b/ethers-solc/src/artifacts/serde_helpers.rs
index 0e38ee3f..75e87ee5 100644
--- a/ethers-solc/src/artifacts/serde_helpers.rs
+++ b/ethers-solc/src/artifacts/serde_helpers.rs
@@ -41,7 +41,7 @@ where
 pub mod json_string_opt {
     use serde::{
         de::{self, DeserializeOwned},
-        ser, Deserialize, Deserializer, Serialize, Serializer,
+        Deserialize, Deserializer, Serialize, Serializer,
     };
 
     pub fn serialize<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
@@ -50,8 +50,7 @@ pub mod json_string_opt {
         T: Serialize,
     {
         if let Some(value) = value {
-            let value = serde_json::to_string(value).map_err(ser::Error::custom)?;
-            serializer.serialize_str(&value)
+            value.serialize(serializer)
         } else {
             serializer.serialize_none()
         }
diff --git a/ethers-solc/src/report/mod.rs b/ethers-solc/src/report/mod.rs
index 4d33285f..4c7a572d 100644
--- a/ethers-solc/src/report/mod.rs
+++ b/ethers-solc/src/report/mod.rs
@@ -253,7 +253,7 @@ where
     CURRENT_STATE
         .try_with(|state| {
             let scoped = state.scoped.borrow_mut();
-            f(&*scoped)
+            f(&scoped)
         })
         .unwrap_or_else(|_| f(&Report::none()))
 }


Test patch:
diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs
index 1093036b..bb849a7f 100644
--- a/ethers-solc/tests/project.rs
+++ b/ethers-solc/tests/project.rs
@@ -1620,10 +1620,9 @@ fn can_compile_model_checker_sample() {
 }
 
 fn remove_solc_if_exists(version: &Version) {
-    match Solc::find_svm_installed_version(version.to_string()).unwrap() {
-        Some(_) => svm::remove_version(version).expect("failed to remove version"),
-        None => {}
-    };
+    if Solc::find_svm_installed_version(version.to_string()).unwrap().is_some() {
+        svm::remove_version(version).expect("failed to remove version")
+    }
 }
 
 #[tokio::test(flavor = "multi_thread")]