Skip to content

Commit 395712a

Browse files
committed
Adding default for v2 and updating configuration to parse etherscan api version from config
1 parent a98d6d0 commit 395712a

File tree

3 files changed

+131
-16
lines changed

3 files changed

+131
-16
lines changed

crates/config/src/etherscan.rs

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use figment::{
1010
Error, Metadata, Profile, Provider,
1111
};
1212
use heck::ToKebabCase;
13+
use foundry_block_explorers::EtherscanApiVersion;
14+
use inflector::Inflector;
1315
use serde::{Deserialize, Deserializer, Serialize, Serializer};
1416
use std::{
1517
collections::BTreeMap,
@@ -173,6 +175,9 @@ pub struct EtherscanConfig {
173175
/// Etherscan API URL
174176
#[serde(default, skip_serializing_if = "Option::is_none")]
175177
pub url: Option<String>,
178+
/// Etherscan API Version. Defaults to v2
179+
#[serde(default, skip_serializing_if = "Option::is_none")]
180+
pub api_version: Option<EtherscanApiVersion>,
176181
/// The etherscan API KEY that's required to make requests
177182
pub key: EtherscanApiKey,
178183
}
@@ -188,7 +193,7 @@ impl EtherscanConfig {
188193
self,
189194
alias: Option<&str>,
190195
) -> Result<ResolvedEtherscanConfig, EtherscanConfigError> {
191-
let Self { chain, mut url, key } = self;
196+
let Self { chain, mut url, key, api_version } = self;
192197

193198
if let Some(url) = &mut url {
194199
*url = interpolate(url)?;
@@ -219,17 +224,27 @@ impl EtherscanConfig {
219224
match (chain, url) {
220225
(Some(chain), Some(api_url)) => Ok(ResolvedEtherscanConfig {
221226
api_url,
227+
api_version: api_version.map(|v| v.to_string()),
222228
browser_url: chain.etherscan_urls().map(|(_, url)| url.to_string()),
223229
key,
224230
chain: Some(chain),
225231
}),
226-
(Some(chain), None) => ResolvedEtherscanConfig::create(key, chain).ok_or_else(|| {
232+
(Some(chain), None) => ResolvedEtherscanConfig::create(
233+
key,
234+
chain,
235+
api_version.map(|v| v.to_string()),
236+
)
237+
.ok_or_else(|| {
227238
let msg = alias.map(|a| format!(" `{a}`")).unwrap_or_default();
228239
EtherscanConfigError::UnknownChain(msg, chain)
229240
}),
230-
(None, Some(api_url)) => {
231-
Ok(ResolvedEtherscanConfig { api_url, browser_url: None, key, chain: None })
232-
}
241+
(None, Some(api_url)) => Ok(ResolvedEtherscanConfig {
242+
api_url,
243+
browser_url: None,
244+
key,
245+
chain: None,
246+
api_version: api_version.map(|v| v.to_string()),
247+
}),
233248
(None, None) => {
234249
let msg = alias
235250
.map(|a| format!(" for Etherscan config with unknown alias `{a}`"))
@@ -251,18 +266,26 @@ pub struct ResolvedEtherscanConfig {
251266
pub browser_url: Option<String>,
252267
/// The resolved API key.
253268
pub key: String,
269+
/// Etherscan API Version.
270+
#[serde(default, skip_serializing_if = "Option::is_none")]
271+
pub api_version: Option<String>,
254272
/// The chain name or EIP-155 chain ID.
255273
#[serde(default, skip_serializing_if = "Option::is_none")]
256274
pub chain: Option<Chain>,
257275
}
258276

259277
impl ResolvedEtherscanConfig {
260278
/// Creates a new instance using the api key and chain
261-
pub fn create(api_key: impl Into<String>, chain: impl Into<Chain>) -> Option<Self> {
279+
pub fn create(
280+
api_key: impl Into<String>,
281+
chain: impl Into<Chain>,
282+
api_version: Option<impl Into<String>>,
283+
) -> Option<Self> {
262284
let chain = chain.into();
263285
let (api_url, browser_url) = chain.etherscan_urls()?;
264286
Some(Self {
265287
api_url: api_url.to_string(),
288+
api_version: api_version.map(|v| v.into()),
266289
browser_url: Some(browser_url.to_string()),
267290
key: api_key.into(),
268291
chain: Some(chain),
@@ -294,7 +317,7 @@ impl ResolvedEtherscanConfig {
294317
self,
295318
) -> Result<foundry_block_explorers::Client, foundry_block_explorers::errors::EtherscanError>
296319
{
297-
let Self { api_url, browser_url, key: api_key, chain } = self;
320+
let Self { api_url, browser_url, key: api_key, chain, api_version } = self;
298321
let (mainnet_api, mainnet_url) = NamedChain::Mainnet.etherscan_urls().expect("exist; qed");
299322

300323
let cache = chain
@@ -310,12 +333,14 @@ impl ResolvedEtherscanConfig {
310333
}
311334

312335
let api_url = into_url(&api_url)?;
336+
let parsed_api_version = EtherscanApiVersion::try_from(api_version.unwrap_or_default())?;
313337
let client = reqwest::Client::builder()
314338
.user_agent(ETHERSCAN_USER_AGENT)
315339
.tls_built_in_root_certs(api_url.scheme() == "https")
316340
.build()?;
317341
foundry_block_explorers::Client::builder()
318342
.with_client(client)
343+
.with_api_version(parsed_api_version)
319344
.with_api_key(api_key)
320345
.with_api_url(api_url)?
321346
// the browser url is not used/required by the client so we can simply set the
@@ -423,12 +448,36 @@ mod tests {
423448
chain: Some(Mainnet.into()),
424449
url: None,
425450
key: EtherscanApiKey::Key("ABCDEFG".to_string()),
451+
api_version: None,
426452
},
427453
);
428454

429455
let mut resolved = configs.resolved();
430456
let config = resolved.remove("mainnet").unwrap().unwrap();
431-
let _ = config.into_client().unwrap();
457+
// None version = None
458+
assert_eq!(config.api_version, None);
459+
let client = config.into_client().unwrap();
460+
assert_eq!(*client.etherscan_api_version(), EtherscanApiVersion::V2);
461+
}
462+
463+
#[test]
464+
fn can_create_v1_client_via_chain() {
465+
let mut configs = EtherscanConfigs::default();
466+
configs.insert(
467+
"mainnet".to_string(),
468+
EtherscanConfig {
469+
chain: Some(Mainnet.into()),
470+
url: None,
471+
api_version: Some(EtherscanApiVersion::V1),
472+
key: EtherscanApiKey::Key("ABCDEG".to_string()),
473+
},
474+
);
475+
476+
let mut resolved = configs.resolved();
477+
let config = resolved.remove("mainnet").unwrap().unwrap();
478+
assert_eq!(config.api_version, Some("v1".to_string()));
479+
let client = config.into_client().unwrap();
480+
assert_eq!(*client.etherscan_api_version(), EtherscanApiVersion::V1);
432481
}
433482

434483
#[test]
@@ -440,6 +489,7 @@ mod tests {
440489
chain: Some(Mainnet.into()),
441490
url: Some("https://api.etherscan.io/api".to_string()),
442491
key: EtherscanApiKey::Key("ABCDEFG".to_string()),
492+
api_version: None,
443493
},
444494
);
445495

@@ -457,6 +507,7 @@ mod tests {
457507
EtherscanConfig {
458508
chain: Some(Mainnet.into()),
459509
url: Some("https://api.etherscan.io/api".to_string()),
510+
api_version: None,
460511
key: EtherscanApiKey::Env(format!("${{{env}}}")),
461512
},
462513
);
@@ -470,7 +521,8 @@ mod tests {
470521
let mut resolved = configs.resolved();
471522
let config = resolved.remove("mainnet").unwrap().unwrap();
472523
assert_eq!(config.key, "ABCDEFG");
473-
let _ = config.into_client().unwrap();
524+
let client = config.into_client().unwrap();
525+
assert_eq!(*client.etherscan_api_version(), EtherscanApiVersion::V2);
474526

475527
std::env::remove_var(env);
476528
}
@@ -484,6 +536,7 @@ mod tests {
484536
chain: None,
485537
url: Some("https://api.etherscan.io/api".to_string()),
486538
key: EtherscanApiKey::Key("ABCDEFG".to_string()),
539+
api_version: None,
487540
},
488541
);
489542

@@ -498,6 +551,7 @@ mod tests {
498551
chain: None,
499552
url: Some("https://api.etherscan.io/api".to_string()),
500553
key: EtherscanApiKey::Key("ABCDEFG".to_string()),
554+
api_version: None,
501555
};
502556
let resolved = config.clone().resolve(Some("base_sepolia")).unwrap();
503557
assert_eq!(resolved.chain, Some(Chain::base_sepolia()));

crates/config/src/lib.rs

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,9 +1137,9 @@ impl Config {
11371137

11381138
/// Whether caching should be enabled for the given chain id
11391139
pub fn enable_caching(&self, endpoint: &str, chain_id: impl Into<u64>) -> bool {
1140-
!self.no_storage_caching &&
1141-
self.rpc_storage_caching.enable_for_chain_id(chain_id.into()) &&
1142-
self.rpc_storage_caching.enable_for_endpoint(endpoint)
1140+
!self.no_storage_caching
1141+
&& self.rpc_storage_caching.enable_for_chain_id(chain_id.into())
1142+
&& self.rpc_storage_caching.enable_for_endpoint(endpoint)
11431143
}
11441144

11451145
/// Returns the `ProjectPathsConfig` sub set of the config.
@@ -1403,7 +1403,11 @@ impl Config {
14031403
// etherscan fallback via API key
14041404
if let Some(key) = self.etherscan_api_key.as_ref() {
14051405
let chain = chain.or(self.chain).unwrap_or_default();
1406-
return Ok(ResolvedEtherscanConfig::create(key, chain));
1406+
return Ok(ResolvedEtherscanConfig::create(
1407+
key,
1408+
chain,
1409+
None::<String>,
1410+
));
14071411
}
14081412

14091413
Ok(None)
@@ -2008,8 +2012,8 @@ impl Config {
20082012
let file_name = block.file_name();
20092013
let filepath = if file_type.is_dir() {
20102014
block.path().join("storage.json")
2011-
} else if file_type.is_file() &&
2012-
file_name.to_string_lossy().chars().all(char::is_numeric)
2015+
} else if file_type.is_file()
2016+
&& file_name.to_string_lossy().chars().all(char::is_numeric)
20132017
{
20142018
block.path()
20152019
} else {
@@ -3077,6 +3081,7 @@ mod tests {
30773081
api_url: mainnet_urls.0.to_string(),
30783082
chain: Some(NamedChain::Mainnet.into()),
30793083
browser_url: Some(mainnet_urls.1.to_string()),
3084+
api_version: None,
30803085
key: "FX42Z3BBJJEWXWGYV2X1CIPRSCN".to_string(),
30813086
}
30823087
),
@@ -3086,6 +3091,62 @@ mod tests {
30863091
api_url: mb_urls.0.to_string(),
30873092
chain: Some(Moonbeam.into()),
30883093
browser_url: Some(mb_urls.1.to_string()),
3094+
api_version: None,
3095+
key: "123456789".to_string(),
3096+
}
3097+
),
3098+
])
3099+
);
3100+
3101+
Ok(())
3102+
});
3103+
}
3104+
3105+
#[test]
3106+
fn test_resolve_etherscan_with_versions() {
3107+
figment::Jail::expect_with(|jail| {
3108+
jail.create_file(
3109+
"foundry.toml",
3110+
r#"
3111+
[profile.default]
3112+
3113+
[etherscan]
3114+
mainnet = { key = "FX42Z3BBJJEWXWGYV2X1CIPRSCN", api_version = "v2" }
3115+
moonbeam = { key = "${_CONFIG_ETHERSCAN_MOONBEAM}", api_version = "v1" }
3116+
"#,
3117+
)?;
3118+
3119+
let config = Config::load().unwrap();
3120+
3121+
assert!(config.etherscan.clone().resolved().has_unresolved());
3122+
3123+
jail.set_env("_CONFIG_ETHERSCAN_MOONBEAM", "123456789");
3124+
3125+
let configs = config.etherscan.resolved();
3126+
assert!(!configs.has_unresolved());
3127+
3128+
let mb_urls = Moonbeam.etherscan_urls().unwrap();
3129+
let mainnet_urls = NamedChain::Mainnet.etherscan_urls().unwrap();
3130+
assert_eq!(
3131+
configs,
3132+
ResolvedEtherscanConfigs::new([
3133+
(
3134+
"mainnet",
3135+
ResolvedEtherscanConfig {
3136+
api_url: mainnet_urls.0.to_string(),
3137+
chain: Some(NamedChain::Mainnet.into()),
3138+
browser_url: Some(mainnet_urls.1.to_string()),
3139+
api_version: Some("v2".to_string()),
3140+
key: "FX42Z3BBJJEWXWGYV2X1CIPRSCN".to_string(),
3141+
}
3142+
),
3143+
(
3144+
"moonbeam",
3145+
ResolvedEtherscanConfig {
3146+
api_url: mb_urls.0.to_string(),
3147+
chain: Some(Moonbeam.into()),
3148+
browser_url: Some(mb_urls.1.to_string()),
3149+
api_version: Some("v1".to_string()),
30893150
key: "123456789".to_string(),
30903151
}
30913152
),

crates/verify/src/etherscan/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,7 @@ mod tests {
593593

594594
assert_eq!(
595595
client.etherscan_api_url().as_str(),
596-
"https://api.etherscan.io/v2/api?chainid=80001"
596+
"https://api.etherscan.io/v2/api"
597597
);
598598
assert!(format!("{client:?}").contains("dummykey"));
599599

0 commit comments

Comments
 (0)