feat(solc): emit additional raw metadata field (#1365)

* feat(solc): emit additional raw metadata field

* chore: update CHANGELOG

* test: updata test

* fix(solc): proper metadata deserialization
This commit is contained in:
Matthias Seitz 2022-06-10 15:49:19 +02:00 committed by GitHub
parent c96f6ba616
commit 980649060a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 74 additions and 7 deletions

View File

@ -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

View File

@ -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<GasEstimates>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub raw_metadata: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub storage_layout: Option<StorageLayout>,
@ -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))?
}
}

View File

@ -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<Metadata>,
pub metadata: Option<LosslessMetadata>,
#[serde(default)]
pub userdoc: UserDoc,
#[serde(default)]

View File

@ -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::Value> {
serde_json::from_str(&self.raw_metadata)
}
}
impl Serialize for LosslessMetadata {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.raw_metadata.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for LosslessMetadata {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, value: &str) -> Result<Self::Value, E>
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 {

View File

@ -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)
}

View File

@ -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());
}