diff --git a/CHANGELOG.md b/CHANGELOG.md index c0dab0e4..2d06ca4d 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 diff --git a/ethers-solc/src/artifact_output/configurable.rs b/ethers-solc/src/artifact_output/configurable.rs index 28120d7a..a964b20e 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 2779e63e..f3a33434 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, }, - serde_helpers, DevDoc, Evm, Ewasm, LosslessAbi, Metadata, 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}; @@ -22,7 +23,7 @@ pub struct Contract { skip_serializing_if = "Option::is_none", with = "serde_helpers::json_string_opt" )] - pub metadata: Option, + 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 acc9229e..7e436b0c 100644 --- a/ethers-solc/src/artifacts/mod.rs +++ b/ethers-solc/src/artifacts/mod.rs @@ -856,6 +856,61 @@ 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>, + { + 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) + } +} + /// Compiler settings #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct MetadataSettings { diff --git a/ethers-solc/src/artifacts/serde_helpers.rs b/ethers-solc/src/artifacts/serde_helpers.rs index 7e664df8..d935e1a9 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) } diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs index 4600d09a..30309cc5 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()); }