From 1c60e768e6a3780adec8ee441ad76da10024bbce Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 9 Jun 2022 13:39:50 +0200 Subject: [PATCH 1/4] feat(solc): emit additional raw metadata field --- .../src/artifact_output/configurable.rs | 14 +++++-- ethers-solc/src/artifacts/contract.rs | 10 ++--- ethers-solc/src/artifacts/mod.rs | 39 +++++++++++++++++++ 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/ethers-solc/src/artifact_output/configurable.rs b/ethers-solc/src/artifact_output/configurable.rs index 28120d7a2..a964b20ef 100644 --- a/ethers-solc/src/artifact_output/configurable.rs +++ b/ethers-solc/src/artifact_output/configurable.rs @@ -9,7 +9,8 @@ use crate::{ EwasmOutputSelection, }, Ast, CompactContractBytecodeCow, DevDoc, Evm, Ewasm, FunctionDebugData, GasEstimates, - GeneratedSource, LosslessAbi, Metadata, Offsets, Settings, StorageLayout, UserDoc, + GeneratedSource, LosslessAbi, LosslessMetadata, Metadata, Offsets, Settings, StorageLayout, + UserDoc, }, sources::VersionedSourceFile, ArtifactOutput, SolcConfig, SolcError, SourceFile, @@ -41,6 +42,8 @@ pub struct ConfigurableContractArtifact { #[serde(default, skip_serializing_if = "Option::is_none")] pub gas_estimates: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub raw_metadata: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub metadata: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub storage_layout: Option, @@ -232,6 +235,7 @@ impl ArtifactOutput for ConfigurableArtifacts { ) -> Self::Artifact { let mut artifact_userdoc = None; let mut artifact_devdoc = None; + let mut artifact_raw_metadata = None; let mut artifact_metadata = None; let mut artifact_ir = None; let mut artifact_ir_optimized = None; @@ -258,7 +262,10 @@ impl ArtifactOutput for ConfigurableArtifacts { } = contract; if self.additional_values.metadata { - artifact_metadata = metadata; + if let Some(LosslessMetadata { raw_metadata, metadata }) = metadata { + artifact_raw_metadata = Some(raw_metadata); + artifact_metadata = Some(metadata); + } } if self.additional_values.userdoc { artifact_userdoc = Some(userdoc); @@ -318,6 +325,7 @@ impl ArtifactOutput for ConfigurableArtifacts { function_debug_data: artifact_function_debug_data, method_identifiers: artifact_method_identifiers, gas_estimates: artifact_gas_estimates, + raw_metadata: artifact_raw_metadata, metadata: artifact_metadata, storage_layout: artifact_storage_layout, userdoc: artifact_userdoc, @@ -564,7 +572,7 @@ impl ExtraOutputFiles { if self.metadata { if let Some(ref metadata) = contract.metadata { let file = file.with_extension("metadata.json"); - fs::write(&file, serde_json::to_string_pretty(metadata)?) + fs::write(&file, serde_json::to_string_pretty(&metadata.raw_json()?)?) .map_err(|err| SolcError::io(err, file))? } } diff --git a/ethers-solc/src/artifacts/contract.rs b/ethers-solc/src/artifacts/contract.rs index 2779e63e0..62ce44eca 100644 --- a/ethers-solc/src/artifacts/contract.rs +++ b/ethers-solc/src/artifacts/contract.rs @@ -4,7 +4,7 @@ use crate::artifacts::{ bytecode::{ Bytecode, BytecodeObject, CompactBytecode, CompactDeployedBytecode, DeployedBytecode, }, - serde_helpers, DevDoc, Evm, Ewasm, LosslessAbi, Metadata, Offsets, StorageLayout, UserDoc, + DevDoc, Evm, Ewasm, LosslessAbi, LosslessMetadata, Offsets, StorageLayout, UserDoc, }; use ethers_core::{abi::Contract as Abi, types::Bytes}; use serde::{Deserialize, Serialize}; @@ -17,12 +17,8 @@ pub struct Contract { /// The Ethereum Contract Metadata. /// See pub abi: Option, - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "serde_helpers::json_string_opt" - )] - pub metadata: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub metadata: Option, #[serde(default)] pub userdoc: UserDoc, #[serde(default)] diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs index acc9229ec..b9ac24c0c 100644 --- a/ethers-solc/src/artifacts/mod.rs +++ b/ethers-solc/src/artifacts/mod.rs @@ -856,6 +856,45 @@ pub struct Metadata { pub version: i64, } +/// A helper type that ensures lossless (de)serialisation so we can preserve the exact String +/// metadata value that's being hashed by solc +#[derive(Clone, Debug, PartialEq)] +pub struct LosslessMetadata { + /// The complete abi as json value + pub raw_metadata: String, + /// The deserialised metadata of `raw_metadata` + pub metadata: Metadata, +} + +// === impl LosslessMetadata === + +impl LosslessMetadata { + /// Returns the whole string raw metadata as `serde_json::Value` + pub fn raw_json(&self) -> serde_json::Result { + serde_json::from_str(&self.raw_metadata) + } +} + +impl Serialize for LosslessMetadata { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.raw_metadata.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for LosslessMetadata { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let raw_metadata = String::deserialize(deserializer)?; + let metadata = serde_json::from_str(&raw_metadata).map_err(serde::de::Error::custom)?; + Ok(Self { raw_metadata, metadata }) + } +} + /// Compiler settings #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct MetadataSettings { From a756561a568514d521425707a18f88941a30ca3b Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 9 Jun 2022 13:43:58 +0200 Subject: [PATCH 2/4] chore: update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0dab0e4c..2d06ca4d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,6 +99,8 @@ ### Unreleased +- Add `rawMetadata:String` field to configurable contract output + [#1365](https://github.com/gakonst/ethers-rs/pull/1365) - Use relative source paths and `solc --base-path` [#1317](https://github.com/gakonst/ethers-rs/pull/1317) - Save cache entry objects with relative paths From 8accb79a3e80facb4784f50fac7c6b017f2b3d27 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 9 Jun 2022 13:45:39 +0200 Subject: [PATCH 3/4] test: updata test --- ethers-solc/tests/project.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs index 4600d09a5..30309cc5b 100644 --- a/ethers-solc/tests/project.rs +++ b/ethers-solc/tests/project.rs @@ -148,6 +148,7 @@ fn can_compile_configured() { let compiled = project.compile().unwrap(); let artifact = compiled.find("Dapp").unwrap(); assert!(artifact.metadata.is_some()); + assert!(artifact.raw_metadata.is_some()); assert!(artifact.ir.is_some()); assert!(artifact.ir_optimized.is_some()); } From 3eb8a537289429b2c980af5049beec3089e8e061 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 9 Jun 2022 17:21:48 +0200 Subject: [PATCH 4/4] fix(solc): proper metadata deserialization --- ethers-solc/src/artifacts/contract.rs | 9 +++++++-- ethers-solc/src/artifacts/mod.rs | 22 +++++++++++++++++++--- ethers-solc/src/artifacts/serde_helpers.rs | 4 ++-- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/ethers-solc/src/artifacts/contract.rs b/ethers-solc/src/artifacts/contract.rs index 62ce44eca..f3a334347 100644 --- a/ethers-solc/src/artifacts/contract.rs +++ b/ethers-solc/src/artifacts/contract.rs @@ -4,7 +4,8 @@ use crate::artifacts::{ bytecode::{ Bytecode, BytecodeObject, CompactBytecode, CompactDeployedBytecode, DeployedBytecode, }, - DevDoc, Evm, Ewasm, LosslessAbi, LosslessMetadata, Offsets, StorageLayout, UserDoc, + serde_helpers, DevDoc, Evm, Ewasm, LosslessAbi, LosslessMetadata, Offsets, StorageLayout, + UserDoc, }; use ethers_core::{abi::Contract as Abi, types::Bytes}; use serde::{Deserialize, Serialize}; @@ -17,7 +18,11 @@ pub struct Contract { /// The Ethereum Contract Metadata. /// See pub abi: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "serde_helpers::json_string_opt" + )] pub metadata: Option, #[serde(default)] pub userdoc: UserDoc, diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs index b9ac24c0c..7e436b0c6 100644 --- a/ethers-solc/src/artifacts/mod.rs +++ b/ethers-solc/src/artifacts/mod.rs @@ -889,9 +889,25 @@ impl<'de> Deserialize<'de> for LosslessMetadata { where D: Deserializer<'de>, { - let raw_metadata = String::deserialize(deserializer)?; - let metadata = serde_json::from_str(&raw_metadata).map_err(serde::de::Error::custom)?; - Ok(Self { raw_metadata, metadata }) + struct LosslessMetadataVisitor; + + impl<'de> Visitor<'de> for LosslessMetadataVisitor { + type Value = LosslessMetadata; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(formatter, "metadata string") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + let raw_metadata = value.to_string(); + let metadata = serde_json::from_str(value).map_err(serde::de::Error::custom)?; + Ok(LosslessMetadata { raw_metadata, metadata }) + } + } + deserializer.deserialize_str(LosslessMetadataVisitor) } } diff --git a/ethers-solc/src/artifacts/serde_helpers.rs b/ethers-solc/src/artifacts/serde_helpers.rs index 7e664df87..d935e1a90 100644 --- a/ethers-solc/src/artifacts/serde_helpers.rs +++ b/ethers-solc/src/artifacts/serde_helpers.rs @@ -66,8 +66,8 @@ pub mod json_string_opt { if s.is_empty() { return Ok(None) } - - serde_json::from_str(&s).map_err(de::Error::custom).map(Some) + let value = serde_json::Value::String(s); + serde_json::from_value(value).map_err(de::Error::custom).map(Some) } else { Ok(None) }