fix(etherscan): source code serde (#1962)

This commit is contained in:
DaniPopes 2022-12-22 19:58:35 +01:00 committed by GitHub
parent 5008006767
commit 1baf88138f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 46 additions and 15 deletions

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
source_tree::{SourceTree, SourceTreeEntry}, source_tree::{SourceTree, SourceTreeEntry},
utils::{deserialize_address_opt, deserialize_stringified_source_code}, utils::{deserialize_address_opt, deserialize_source_code},
Client, EtherscanError, Response, Result, Client, EtherscanError, Response, Result,
}; };
use ethers_core::{ use ethers_core::{
@ -107,7 +107,7 @@ impl SourceCodeMetadata {
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
pub struct Metadata { pub struct Metadata {
/// Includes metadata for compiler settings and language. /// Includes metadata for compiler settings and language.
#[serde(deserialize_with = "deserialize_stringified_source_code")] #[serde(deserialize_with = "deserialize_source_code")]
pub source_code: SourceCodeMetadata, pub source_code: SourceCodeMetadata,
/// The ABI of the contract. /// The ABI of the contract.
@ -148,7 +148,11 @@ pub struct Metadata {
pub proxy: u64, pub proxy: u64,
/// If this contract is a proxy, the address of its implementation. /// If this contract is a proxy, the address of its implementation.
#[serde(deserialize_with = "deserialize_address_opt")] #[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_address_opt"
)]
pub implementation: Option<Address>, pub implementation: Option<Address>,
/// The swarm source of the contract. /// The swarm source of the contract.

View File

@ -36,15 +36,27 @@ pub fn deserialize_address_opt<'de, D: Deserializer<'de>>(
/// Deserializes as JSON: /// Deserializes as JSON:
/// ///
/// `{ "SourceCode": "{{ .. }}", ..}` /// Object: `{ "SourceCode": { language: "Solidity", .. }, ..}`
/// ///
/// or /// or
/// ///
/// `{ "SourceCode": "..", .. }` /// Stringified JSON: `{ "SourceCode": "{{\r\n \"language\": \"Solidity\", ..}}", ..}`
pub fn deserialize_stringified_source_code<'de, D: Deserializer<'de>>( ///
/// or
///
/// Normal source code: `{ "SourceCode": "// SPDX-License-Identifier: ...", .. }`
pub fn deserialize_source_code<'de, D: Deserializer<'de>>(
deserializer: D, deserializer: D,
) -> std::result::Result<SourceCodeMetadata, D::Error> { ) -> std::result::Result<SourceCodeMetadata, D::Error> {
let s = String::deserialize(deserializer)?; #[derive(Deserialize)]
#[serde(untagged)]
enum SourceCode {
String(String), // this must come first
Obj(SourceCodeMetadata),
}
let s = SourceCode::deserialize(deserializer)?;
match s {
SourceCode::String(s) => {
if s.starts_with("{{") && s.ends_with("}}") { if s.starts_with("{{") && s.ends_with("}}") {
let s = &s[1..s.len() - 1]; let s = &s[1..s.len() - 1];
serde_json::from_str(s).map_err(serde::de::Error::custom) serde_json::from_str(s).map_err(serde::de::Error::custom)
@ -52,6 +64,9 @@ pub fn deserialize_stringified_source_code<'de, D: Deserializer<'de>>(
Ok(SourceCodeMetadata::SourceCode(s)) Ok(SourceCodeMetadata::SourceCode(s))
} }
} }
SourceCode::Obj(obj) => Ok(obj),
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -108,15 +123,27 @@ mod tests {
} }
#[test] #[test]
fn can_deserialize_stringified_source_code() { fn can_deserialize_source_code() {
#[derive(Deserialize)] #[derive(Deserialize)]
struct Test { struct Test {
#[serde(deserialize_with = "deserialize_stringified_source_code")] #[serde(deserialize_with = "deserialize_source_code")]
source_code: SourceCodeMetadata, source_code: SourceCodeMetadata,
} }
let src = "source code text"; let src = "source code text";
// Normal JSON
let json = r#"{
"source_code": { "language": "Solidity", "sources": { "Contract": { "content": "source code text" } } }
}"#;
let de: Test = serde_json::from_str(json).unwrap();
assert!(matches!(de.source_code.language().unwrap(), SourceCodeLanguage::Solidity));
assert_eq!(de.source_code.sources().len(), 1);
assert_eq!(de.source_code.sources().get("Contract").unwrap().content, src);
#[cfg(feature = "ethers-solc")]
assert!(matches!(de.source_code.settings().unwrap(), None));
// Stringified JSON
let json = r#"{ let json = r#"{
"source_code": "{{ \"language\": \"Solidity\", \"sources\": { \"Contract\": { \"content\": \"source code text\" } } }}" "source_code": "{{ \"language\": \"Solidity\", \"sources\": { \"Contract\": { \"content\": \"source code text\" } } }}"
}"#; }"#;