Skip to content

Commit 6f81bb9

Browse files
committed
Adding default for v2 and updating configuration to parse etherscan api version from config
1 parent 324f866 commit 6f81bb9

File tree

3 files changed

+130
-16
lines changed

3 files changed

+130
-16
lines changed

crates/config/src/etherscan.rs

+62-9
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use figment::{
99
value::{Dict, Map},
1010
Error, Metadata, Profile, Provider,
1111
};
12+
use foundry_block_explorers::EtherscanApiVersion;
1213
use inflector::Inflector;
1314
use serde::{Deserialize, Deserializer, Serialize, Serializer};
1415
use std::{
@@ -173,6 +174,9 @@ pub struct EtherscanConfig {
173174
/// Etherscan API URL
174175
#[serde(default, skip_serializing_if = "Option::is_none")]
175176
pub url: Option<String>,
177+
/// Etherscan API Version. Defaults to v2
178+
#[serde(default, skip_serializing_if = "Option::is_none")]
179+
pub api_version: Option<EtherscanApiVersion>,
176180
/// The etherscan API KEY that's required to make requests
177181
pub key: EtherscanApiKey,
178182
}
@@ -188,7 +192,7 @@ impl EtherscanConfig {
188192
self,
189193
alias: Option<&str>,
190194
) -> Result<ResolvedEtherscanConfig, EtherscanConfigError> {
191-
let Self { chain, mut url, key } = self;
195+
let Self { chain, mut url, key, api_version } = self;
192196

193197
if let Some(url) = &mut url {
194198
*url = interpolate(url)?;
@@ -219,17 +223,27 @@ impl EtherscanConfig {
219223
match (chain, url) {
220224
(Some(chain), Some(api_url)) => Ok(ResolvedEtherscanConfig {
221225
api_url,
226+
api_version: api_version.map(|v| v.to_string()),
222227
browser_url: chain.etherscan_urls().map(|(_, url)| url.to_string()),
223228
key,
224229
chain: Some(chain),
225230
}),
226-
(Some(chain), None) => ResolvedEtherscanConfig::create(key, chain).ok_or_else(|| {
231+
(Some(chain), None) => ResolvedEtherscanConfig::create(
232+
key,
233+
chain,
234+
api_version.map(|v| v.to_string()),
235+
)
236+
.ok_or_else(|| {
227237
let msg = alias.map(|a| format!(" `{a}`")).unwrap_or_default();
228238
EtherscanConfigError::UnknownChain(msg, chain)
229239
}),
230-
(None, Some(api_url)) => {
231-
Ok(ResolvedEtherscanConfig { api_url, browser_url: None, key, chain: None })
232-
}
240+
(None, Some(api_url)) => Ok(ResolvedEtherscanConfig {
241+
api_url,
242+
browser_url: None,
243+
key,
244+
chain: None,
245+
api_version: api_version.map(|v| v.to_string()),
246+
}),
233247
(None, None) => {
234248
let msg = alias
235249
.map(|a| format!(" for Etherscan config with unknown alias `{a}`"))
@@ -251,18 +265,26 @@ pub struct ResolvedEtherscanConfig {
251265
pub browser_url: Option<String>,
252266
/// The resolved API key.
253267
pub key: String,
268+
/// Etherscan API Version.
269+
#[serde(default, skip_serializing_if = "Option::is_none")]
270+
pub api_version: Option<String>,
254271
/// The chain name or EIP-155 chain ID.
255272
#[serde(default, skip_serializing_if = "Option::is_none")]
256273
pub chain: Option<Chain>,
257274
}
258275

259276
impl ResolvedEtherscanConfig {
260277
/// 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> {
278+
pub fn create(
279+
api_key: impl Into<String>,
280+
chain: impl Into<Chain>,
281+
api_version: Option<impl Into<String>>,
282+
) -> Option<Self> {
262283
let chain = chain.into();
263284
let (api_url, browser_url) = chain.etherscan_urls()?;
264285
Some(Self {
265286
api_url: api_url.to_string(),
287+
api_version: api_version.map(|v| v.into()),
266288
browser_url: Some(browser_url.to_string()),
267289
key: api_key.into(),
268290
chain: Some(chain),
@@ -294,7 +316,7 @@ impl ResolvedEtherscanConfig {
294316
self,
295317
) -> Result<foundry_block_explorers::Client, foundry_block_explorers::errors::EtherscanError>
296318
{
297-
let Self { api_url, browser_url, key: api_key, chain } = self;
319+
let Self { api_url, browser_url, key: api_key, chain, api_version } = self;
298320
let (mainnet_api, mainnet_url) = NamedChain::Mainnet.etherscan_urls().expect("exist; qed");
299321

300322
let cache = chain
@@ -310,12 +332,14 @@ impl ResolvedEtherscanConfig {
310332
}
311333

312334
let api_url = into_url(&api_url)?;
335+
let parsed_api_version = EtherscanApiVersion::try_from(api_version.unwrap_or_default())?;
313336
let client = reqwest::Client::builder()
314337
.user_agent(ETHERSCAN_USER_AGENT)
315338
.tls_built_in_root_certs(api_url.scheme() == "https")
316339
.build()?;
317340
foundry_block_explorers::Client::builder()
318341
.with_client(client)
342+
.with_api_version(parsed_api_version)
319343
.with_api_key(api_key)
320344
.with_api_url(api_url)?
321345
// the browser url is not used/required by the client so we can simply set the
@@ -423,12 +447,36 @@ mod tests {
423447
chain: Some(Mainnet.into()),
424448
url: None,
425449
key: EtherscanApiKey::Key("ABCDEFG".to_string()),
450+
api_version: None,
426451
},
427452
);
428453

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

434482
#[test]
@@ -440,6 +488,7 @@ mod tests {
440488
chain: Some(Mainnet.into()),
441489
url: Some("https://api.etherscan.io/api".to_string()),
442490
key: EtherscanApiKey::Key("ABCDEFG".to_string()),
491+
api_version: None,
443492
},
444493
);
445494

@@ -457,6 +506,7 @@ mod tests {
457506
EtherscanConfig {
458507
chain: Some(Mainnet.into()),
459508
url: Some("https://api.etherscan.io/api".to_string()),
509+
api_version: None,
460510
key: EtherscanApiKey::Env(format!("${{{env}}}")),
461511
},
462512
);
@@ -470,7 +520,8 @@ mod tests {
470520
let mut resolved = configs.resolved();
471521
let config = resolved.remove("mainnet").unwrap().unwrap();
472522
assert_eq!(config.key, "ABCDEFG");
473-
let _ = config.into_client().unwrap();
523+
let client = config.into_client().unwrap();
524+
assert_eq!(*client.etherscan_api_version(), EtherscanApiVersion::V2);
474525

475526
std::env::remove_var(env);
476527
}
@@ -484,6 +535,7 @@ mod tests {
484535
chain: None,
485536
url: Some("https://api.etherscan.io/api".to_string()),
486537
key: EtherscanApiKey::Key("ABCDEFG".to_string()),
538+
api_version: None,
487539
},
488540
);
489541

@@ -498,6 +550,7 @@ mod tests {
498550
chain: None,
499551
url: Some("https://api.etherscan.io/api".to_string()),
500552
key: EtherscanApiKey::Key("ABCDEFG".to_string()),
553+
api_version: None,
501554
};
502555
let resolved = config.clone().resolve(Some("base_sepolia")).unwrap();
503556
assert_eq!(resolved.chain, Some(Chain::base_sepolia()));

crates/config/src/lib.rs

+67-6
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 {
@@ -3055,6 +3059,7 @@ mod tests {
30553059
api_url: mainnet_urls.0.to_string(),
30563060
chain: Some(NamedChain::Mainnet.into()),
30573061
browser_url: Some(mainnet_urls.1.to_string()),
3062+
api_version: None,
30583063
key: "FX42Z3BBJJEWXWGYV2X1CIPRSCN".to_string(),
30593064
}
30603065
),
@@ -3064,6 +3069,62 @@ mod tests {
30643069
api_url: mb_urls.0.to_string(),
30653070
chain: Some(Moonbeam.into()),
30663071
browser_url: Some(mb_urls.1.to_string()),
3072+
api_version: None,
3073+
key: "123456789".to_string(),
3074+
}
3075+
),
3076+
])
3077+
);
3078+
3079+
Ok(())
3080+
});
3081+
}
3082+
3083+
#[test]
3084+
fn test_resolve_etherscan_with_versions() {
3085+
figment::Jail::expect_with(|jail| {
3086+
jail.create_file(
3087+
"foundry.toml",
3088+
r#"
3089+
[profile.default]
3090+
3091+
[etherscan]
3092+
mainnet = { key = "FX42Z3BBJJEWXWGYV2X1CIPRSCN", api_version = "v2" }
3093+
moonbeam = { key = "${_CONFIG_ETHERSCAN_MOONBEAM}", api_version = "v1" }
3094+
"#,
3095+
)?;
3096+
3097+
let config = Config::load().unwrap();
3098+
3099+
assert!(config.etherscan.clone().resolved().has_unresolved());
3100+
3101+
jail.set_env("_CONFIG_ETHERSCAN_MOONBEAM", "123456789");
3102+
3103+
let configs = config.etherscan.resolved();
3104+
assert!(!configs.has_unresolved());
3105+
3106+
let mb_urls = Moonbeam.etherscan_urls().unwrap();
3107+
let mainnet_urls = NamedChain::Mainnet.etherscan_urls().unwrap();
3108+
assert_eq!(
3109+
configs,
3110+
ResolvedEtherscanConfigs::new([
3111+
(
3112+
"mainnet",
3113+
ResolvedEtherscanConfig {
3114+
api_url: mainnet_urls.0.to_string(),
3115+
chain: Some(NamedChain::Mainnet.into()),
3116+
browser_url: Some(mainnet_urls.1.to_string()),
3117+
api_version: Some("v2".to_string()),
3118+
key: "FX42Z3BBJJEWXWGYV2X1CIPRSCN".to_string(),
3119+
}
3120+
),
3121+
(
3122+
"moonbeam",
3123+
ResolvedEtherscanConfig {
3124+
api_url: mb_urls.0.to_string(),
3125+
chain: Some(Moonbeam.into()),
3126+
browser_url: Some(mb_urls.1.to_string()),
3127+
api_version: Some("v1".to_string()),
30673128
key: "123456789".to_string(),
30683129
}
30693130
),

crates/verify/src/etherscan/mod.rs

+1-1
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)