1use crate::{contract::SourceCodeMetadata, units::Units, EtherscanError, Result};
2use alloy_primitives::{Address, ParseSignedError, I256, U256};
3use semver::Version;
4use serde::{Deserialize, Deserializer};
5use std::{fmt, str::FromStr};
6use thiserror::Error;
7
8static SOLC_BIN_LIST_URL: &str = "https://binaries.soliditylang.org/bin/list.txt";
9
10pub async fn lookup_compiler_version(version: &Version) -> Result<Version> {
13 let response = reqwest::get(SOLC_BIN_LIST_URL).await?.text().await?;
14 let version = format!("{}.{}.{}", version.major, version.minor, version.patch);
16 let v = response
17 .lines()
18 .find(|l| !l.contains("nightly") && l.contains(&version))
19 .map(|l| l.trim_start_matches("soljson-v").trim_end_matches(".js"))
20 .ok_or_else(|| EtherscanError::MissingSolcVersion(version))?;
21
22 Ok(v.parse().expect("failed to parse semver"))
23}
24
25pub fn deserialize_address_opt<'de, D: Deserializer<'de>>(
27 deserializer: D,
28) -> std::result::Result<Option<Address>, D::Error> {
29 match Option::<String>::deserialize(deserializer)? {
30 None => Ok(None),
31 Some(s) => match s.is_empty() {
32 true => Ok(None),
33 _ => Ok(Some(s.parse().map_err(serde::de::Error::custom)?)),
34 },
35 }
36}
37
38pub fn deserialize_source_code<'de, D: Deserializer<'de>>(
46 deserializer: D,
47) -> std::result::Result<SourceCodeMetadata, D::Error> {
48 #[derive(Deserialize)]
49 #[serde(untagged)]
50 enum SourceCode {
51 String(String), Obj(SourceCodeMetadata),
53 }
54 let s = SourceCode::deserialize(deserializer)?;
55 match s {
56 SourceCode::String(s) => {
57 if s.starts_with('{') && s.ends_with('}') {
58 let mut s = s.as_str();
59 if s.starts_with("{{") && s.ends_with("}}") {
61 s = &s[1..s.len() - 1];
62 }
63 serde_json::from_str(s).map_err(serde::de::Error::custom)
64 } else {
65 Ok(SourceCodeMetadata::SourceCode(s))
66 }
67 }
68 SourceCode::Obj(obj) => Ok(obj),
69 }
70}
71
72#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
75pub enum ParseUnits {
76 U256(U256),
77 I256(I256),
78}
79
80impl From<ParseUnits> for U256 {
81 fn from(n: ParseUnits) -> Self {
82 match n {
83 ParseUnits::U256(n) => n,
84 ParseUnits::I256(n) => n.into_raw(),
85 }
86 }
87}
88
89impl From<ParseUnits> for I256 {
90 fn from(n: ParseUnits) -> Self {
91 match n {
92 ParseUnits::I256(n) => n,
93 ParseUnits::U256(n) => I256::from_raw(n),
94 }
95 }
96}
97
98impl From<alloy_primitives::Signed<256, 4>> for ParseUnits {
99 fn from(n: alloy_primitives::Signed<256, 4>) -> Self {
100 Self::I256(n)
101 }
102}
103
104impl fmt::Display for ParseUnits {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 match self {
107 ParseUnits::U256(val) => val.fmt(f),
108 ParseUnits::I256(val) => val.fmt(f),
109 }
110 }
111}
112
113macro_rules! construct_format_units_from {
114 ($( $t:ty[$convert:ident] ),*) => {
115 $(
116 impl From<$t> for ParseUnits {
117 fn from(num: $t) -> Self {
118 Self::$convert(U256::from(num))
119 }
120 }
121 )*
122 }
123}
124
125macro_rules! construct_signed_format_units_from {
126 ($( $t:ty[$convert:ident] ),*) => {
127 $(
128 impl From<$t> for ParseUnits {
129 fn from(num: $t) -> Self {
130 Self::$convert(I256::from_raw(U256::from(num)))
131 }
132 }
133 )*
134 }
135}
136
137construct_format_units_from! {
139 u8[U256], u16[U256], u32[U256], u64[U256], u128[U256], U256[U256], usize[U256]
140}
141
142construct_signed_format_units_from! {
143 i8[I256], i16[I256], i32[I256], i64[I256], i128[I256], isize[I256]
144}
145
146#[derive(Error, Debug)]
147pub enum ConversionError {
148 #[error("Unknown units: {0}")]
149 UnrecognizedUnits(String),
150 #[error("bytes32 strings must not exceed 32 bytes in length")]
151 TextTooLong,
152 #[error(transparent)]
153 Utf8Error(#[from] std::str::Utf8Error),
154 #[error(transparent)]
155 InvalidFloat(#[from] std::num::ParseFloatError),
156 #[error("Invalid decimal string: {0}")]
157 FromDecStrError(String),
158 #[error("Overflow parsing string")]
159 ParseOverflow,
160 #[error("Parse Signed Error")]
161 ParseI256Error(#[from] ParseSignedError),
162 #[error("Invalid address checksum")]
163 InvalidAddressChecksum,
164 #[error(transparent)]
165 FromHexError(<Address as std::str::FromStr>::Err),
166}
167
168pub fn parse_units<K, S>(amount: S, units: K) -> Result<ParseUnits, ConversionError>
170where
171 S: ToString,
172 K: TryInto<Units, Error = ConversionError> + Copy,
173{
174 let exponent: u32 = units.try_into()?.as_num();
175 let mut amount_str = amount.to_string().replace('_', "");
176 let negative = amount_str.chars().next().unwrap_or_default() == '-';
177 let dec_len = if let Some(di) = amount_str.find('.') {
178 amount_str.remove(di);
179 amount_str[di..].len() as u32
180 } else {
181 0
182 };
183
184 if dec_len > exponent {
185 let amount_str = &amount_str[..(amount_str.len() - (dec_len - exponent) as usize)];
187 if negative {
188 if amount_str == "-" {
191 Ok(ParseUnits::I256(I256::ZERO))
192 } else {
193 Ok(ParseUnits::I256(
194 I256::from_dec_str(amount_str)
195 .map_err(|e| ConversionError::FromDecStrError(e.to_string()))?,
196 ))
197 }
198 } else {
199 Ok(ParseUnits::U256(
200 U256::from_str(amount_str)
201 .map_err(|e| ConversionError::FromDecStrError(e.to_string()))?,
202 ))
203 }
204 } else if negative {
205 if amount_str == "-" {
207 Ok(ParseUnits::I256(I256::ZERO))
208 } else {
209 let _fi = U256::from(10_i64);
210 let mut n = I256::from_str(&amount_str)?;
211 n *= I256::from_raw(U256::from(10))
212 .checked_pow(U256::from(exponent) - U256::from(dec_len))
213 .ok_or(ConversionError::ParseOverflow)?;
214 Ok(ParseUnits::I256(n))
215 }
216 } else {
217 let mut a_uint = U256::from_str(&amount_str)
218 .map_err(|e| ConversionError::FromDecStrError(e.to_string()))?;
219 a_uint *= U256::from(10)
220 .checked_pow(U256::from(exponent - dec_len))
221 .ok_or(ConversionError::ParseOverflow)?;
222 Ok(ParseUnits::U256(a_uint))
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 use crate::contract::SourceCodeLanguage;
230
231 #[test]
232 fn can_deserialize_address_opt() {
233 #[derive(serde::Serialize, Deserialize)]
234 struct Test {
235 #[serde(deserialize_with = "deserialize_address_opt")]
236 address: Option<Address>,
237 }
238
239 let json = r#"{"address":""}"#;
241 let de: Test = serde_json::from_str(json).unwrap();
242 assert_eq!(de.address, None);
243
244 let json = serde_json::to_string(&de).unwrap();
246 let de: Test = serde_json::from_str(&json).unwrap();
247 assert_eq!(de.address, None);
248
249 let json = r#"{"address":"0x4af649ffde640ceb34b1afaba3e0bb8e9698cb01"}"#;
251 let de: Test = serde_json::from_str(json).unwrap();
252 let expected = "0x4af649ffde640ceb34b1afaba3e0bb8e9698cb01".parse().unwrap();
253 assert_eq!(de.address, Some(expected));
254 }
255
256 #[test]
257 fn can_deserialize_source_code() {
258 #[derive(Deserialize)]
259 struct Test {
260 #[serde(deserialize_with = "deserialize_source_code")]
261 source_code: SourceCodeMetadata,
262 }
263
264 let src = "source code text";
265
266 let json = r#"{
268 "source_code": { "language": "Solidity", "sources": { "Contract": { "content": "source code text" } } }
269 }"#;
270 let de: Test = serde_json::from_str(json).unwrap();
271 assert!(matches!(de.source_code.language().unwrap(), SourceCodeLanguage::Solidity));
272 assert_eq!(de.source_code.sources().len(), 1);
273 assert_eq!(de.source_code.sources().get("Contract").unwrap().content, src);
274 #[cfg(feature = "foundry-compilers")]
275 assert!(de.source_code.settings().unwrap().is_none());
276
277 let json = r#"{
279 "source_code": "{{ \"language\": \"Solidity\", \"sources\": { \"Contract\": { \"content\": \"source code text\" } } }}"
280 }"#;
281 let de: Test = serde_json::from_str(json).unwrap();
282 assert!(matches!(de.source_code.language().unwrap(), SourceCodeLanguage::Solidity));
283 assert_eq!(de.source_code.sources().len(), 1);
284 assert_eq!(de.source_code.sources().get("Contract").unwrap().content, src);
285 #[cfg(feature = "foundry-compilers")]
286 assert!(de.source_code.settings().unwrap().is_none());
287
288 let json = r#"{"source_code": "source code text"}"#;
289 let de: Test = serde_json::from_str(json).unwrap();
290 assert_eq!(de.source_code.source_code(), src);
291 }
292}