ethers_etherscan/
utils.rs1use crate::{contract::SourceCodeMetadata, EtherscanError, Result};
2use ethers_core::types::Address;
3use semver::Version;
4use serde::{Deserialize, Deserializer};
5
6static SOLC_BIN_LIST_URL: &str = "https://binaries.soliditylang.org/bin/list.txt";
7
8pub async fn lookup_compiler_version(version: &Version) -> Result<Version> {
11 let response = reqwest::get(SOLC_BIN_LIST_URL).await?.text().await?;
12 let version = format!("{}.{}.{}", version.major, version.minor, version.patch);
14 let v = response
15 .lines()
16 .find(|l| !l.contains("nightly") && l.contains(&version))
17 .map(|l| l.trim_start_matches("soljson-v").trim_end_matches(".js"))
18 .ok_or_else(|| EtherscanError::MissingSolcVersion(version))?;
19
20 Ok(v.parse().expect("failed to parse semver"))
21}
22
23pub fn deserialize_address_opt<'de, D: Deserializer<'de>>(
25 deserializer: D,
26) -> std::result::Result<Option<Address>, D::Error> {
27 match Option::<String>::deserialize(deserializer)? {
28 None => Ok(None),
29 Some(s) => match s.is_empty() {
30 true => Ok(None),
31 _ => Ok(Some(s.parse().map_err(serde::de::Error::custom)?)),
32 },
33 }
34}
35
36pub fn deserialize_source_code<'de, D: Deserializer<'de>>(
44 deserializer: D,
45) -> std::result::Result<SourceCodeMetadata, D::Error> {
46 #[derive(Deserialize)]
47 #[serde(untagged)]
48 enum SourceCode {
49 String(String), Obj(SourceCodeMetadata),
51 }
52 let s = SourceCode::deserialize(deserializer)?;
53 match s {
54 SourceCode::String(s) => {
55 if s.starts_with('{') && s.ends_with('}') {
56 let mut s = s.as_str();
57 if s.starts_with("{{") && s.ends_with("}}") {
59 s = &s[1..s.len() - 1];
60 }
61 serde_json::from_str(s).map_err(serde::de::Error::custom)
62 } else {
63 Ok(SourceCodeMetadata::SourceCode(s))
64 }
65 }
66 SourceCode::Obj(obj) => Ok(obj),
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use crate::contract::SourceCodeLanguage;
74
75 #[test]
76 fn can_deserialize_address_opt() {
77 #[derive(serde::Serialize, Deserialize)]
78 struct Test {
79 #[serde(deserialize_with = "deserialize_address_opt")]
80 address: Option<Address>,
81 }
82
83 let json = r#"{"address":""}"#;
85 let de: Test = serde_json::from_str(json).unwrap();
86 assert_eq!(de.address, None);
87
88 let json = serde_json::to_string(&de).unwrap();
90 let de: Test = serde_json::from_str(&json).unwrap();
91 assert_eq!(de.address, None);
92
93 let json = r#"{"address":"0x4af649ffde640ceb34b1afaba3e0bb8e9698cb01"}"#;
95 let de: Test = serde_json::from_str(json).unwrap();
96 let expected = "0x4af649ffde640ceb34b1afaba3e0bb8e9698cb01".parse().unwrap();
97 assert_eq!(de.address, Some(expected));
98 }
99
100 #[test]
101 fn can_deserialize_source_code() {
102 #[derive(Deserialize)]
103 struct Test {
104 #[serde(deserialize_with = "deserialize_source_code")]
105 source_code: SourceCodeMetadata,
106 }
107
108 let src = "source code text";
109
110 let json = r#"{
112 "source_code": { "language": "Solidity", "sources": { "Contract": { "content": "source code text" } } }
113 }"#;
114 let de: Test = serde_json::from_str(json).unwrap();
115 assert!(matches!(de.source_code.language().unwrap(), SourceCodeLanguage::Solidity));
116 assert_eq!(de.source_code.sources().len(), 1);
117 assert_eq!(de.source_code.sources().get("Contract").unwrap().content, src);
118 #[cfg(feature = "ethers-solc")]
119 assert!(de.source_code.settings().unwrap().is_none());
120
121 let json = r#"{
123 "source_code": "{{ \"language\": \"Solidity\", \"sources\": { \"Contract\": { \"content\": \"source code text\" } } }}"
124 }"#;
125 let de: Test = serde_json::from_str(json).unwrap();
126 assert!(matches!(de.source_code.language().unwrap(), SourceCodeLanguage::Solidity));
127 assert_eq!(de.source_code.sources().len(), 1);
128 assert_eq!(de.source_code.sources().get("Contract").unwrap().content, src);
129 #[cfg(feature = "ethers-solc")]
130 assert!(de.source_code.settings().unwrap().is_none());
131
132 let json = r#"{"source_code": "source code text"}"#;
133 let de: Test = serde_json::from_str(json).unwrap();
134 assert_eq!(de.source_code.source_code(), src);
135 }
136}