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::{
source_tree::{SourceTree, SourceTreeEntry},
utils::{deserialize_address_opt, deserialize_stringified_source_code},
utils::{deserialize_address_opt, deserialize_source_code},
Client, EtherscanError, Response, Result,
};
use ethers_core::{
@ -107,7 +107,7 @@ impl SourceCodeMetadata {
#[serde(rename_all = "PascalCase")]
pub struct Metadata {
/// Includes metadata for compiler settings and language.
#[serde(deserialize_with = "deserialize_stringified_source_code")]
#[serde(deserialize_with = "deserialize_source_code")]
pub source_code: SourceCodeMetadata,
/// The ABI of the contract.
@ -148,7 +148,11 @@ pub struct Metadata {
pub proxy: u64,
/// 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>,
/// The swarm source of the contract.

View File

@ -36,20 +36,35 @@ pub fn deserialize_address_opt<'de, D: Deserializer<'de>>(
/// Deserializes as JSON:
///
/// `{ "SourceCode": "{{ .. }}", ..}`
/// Object: `{ "SourceCode": { language: "Solidity", .. }, ..}`
///
/// or
///
/// `{ "SourceCode": "..", .. }`
pub fn deserialize_stringified_source_code<'de, D: Deserializer<'de>>(
/// Stringified JSON: `{ "SourceCode": "{{\r\n \"language\": \"Solidity\", ..}}", ..}`
///
/// or
///
/// Normal source code: `{ "SourceCode": "// SPDX-License-Identifier: ...", .. }`
pub fn deserialize_source_code<'de, D: Deserializer<'de>>(
deserializer: D,
) -> std::result::Result<SourceCodeMetadata, D::Error> {
let s = String::deserialize(deserializer)?;
if s.starts_with("{{") && s.ends_with("}}") {
let s = &s[1..s.len() - 1];
serde_json::from_str(s).map_err(serde::de::Error::custom)
} else {
Ok(SourceCodeMetadata::SourceCode(s))
#[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("}}") {
let s = &s[1..s.len() - 1];
serde_json::from_str(s).map_err(serde::de::Error::custom)
} else {
Ok(SourceCodeMetadata::SourceCode(s))
}
}
SourceCode::Obj(obj) => Ok(obj),
}
}
@ -108,17 +123,29 @@ mod tests {
}
#[test]
fn can_deserialize_stringified_source_code() {
fn can_deserialize_source_code() {
#[derive(Deserialize)]
struct Test {
#[serde(deserialize_with = "deserialize_stringified_source_code")]
#[serde(deserialize_with = "deserialize_source_code")]
source_code: SourceCodeMetadata,
}
let src = "source code text";
// Normal JSON
let json = r#"{
"source_code": "{{ \"language\": \"Solidity\", \"sources\": {\"Contract\": { \"content\": \"source code text\" } } }}"
"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#"{
"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));