Skip to content

Commit 0f87460

Browse files
grandizzyiainnash
andauthored
feat: Etherscan V2 support (#10440)
* Adding support to etherscanv2 * clippy+fmt * Adding default for v2 and updating configuration to parse etherscan api version from config * Updating api_version to use new variable and fix merge * Use block explorer rev, fix fmt * fix api version parsing * fix fmt * Simplify Etherscan provider option, default v2 * Use released version * Updates, fix script --verify * Clone api version, cast * Cast fixes * Tests nits * configs for verify check * Simplify, use EtherscanApiVersion enum --------- Co-authored-by: Iain Nash <[email protected]>
1 parent c2c4b77 commit 0f87460

File tree

29 files changed

+449
-186
lines changed

29 files changed

+449
-186
lines changed

Cargo.lock

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ foundry-wallets = { path = "crates/wallets" }
188188
foundry-linking = { path = "crates/linking" }
189189

190190
# solc & compilation utilities
191-
foundry-block-explorers = { version = "0.13.0", default-features = false }
191+
foundry-block-explorers = { version = "0.13.3", default-features = false }
192192
foundry-compilers = { version = "0.14.0", default-features = false }
193193
foundry-fork-db = "0.12"
194194
solang-parser = "=0.3.3"

crates/anvil/tests/it/fork.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1324,6 +1324,7 @@ async fn test_fork_execution_reverted() {
13241324

13251325
// <https://github.com/foundry-rs/foundry/issues/8227>
13261326
#[tokio::test(flavor = "multi_thread")]
1327+
#[ignore]
13271328
async fn test_immutable_fork_transaction_hash() {
13281329
use std::str::FromStr;
13291330

crates/cast/src/cmd/artifact.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
use super::{
2-
creation_code::{fetch_creation_code, parse_code_output},
2+
creation_code::{fetch_creation_code_from_etherscan, parse_code_output},
33
interface::{fetch_abi_from_etherscan, load_abi_from_file},
44
};
55
use alloy_primitives::Address;
66
use alloy_provider::Provider;
77
use clap::{command, Parser};
88
use eyre::Result;
9-
use foundry_block_explorers::Client;
109
use foundry_cli::{
1110
opts::{EtherscanOpts, RpcOpts},
1211
utils::{self, LoadConfig},
@@ -46,15 +45,12 @@ pub struct ArtifactArgs {
4645

4746
impl ArtifactArgs {
4847
pub async fn run(self) -> Result<()> {
49-
let Self { contract, etherscan, rpc, output: output_location, abi_path } = self;
48+
let Self { contract, mut etherscan, rpc, output: output_location, abi_path } = self;
5049

51-
let mut etherscan = etherscan;
5250
let config = rpc.load_config()?;
5351
let provider = utils::get_provider(&config)?;
54-
let api_key = etherscan.key().unwrap_or_default();
5552
let chain = provider.get_chain_id().await?;
5653
etherscan.chain = Some(chain.into());
57-
let client = Client::new(chain.into(), api_key)?;
5854

5955
let abi = if let Some(ref abi_path) = abi_path {
6056
load_abi_from_file(abi_path, None)?
@@ -64,7 +60,7 @@ impl ArtifactArgs {
6460

6561
let (abi, _) = abi.first().ok_or_else(|| eyre::eyre!("No ABI found"))?;
6662

67-
let bytecode = fetch_creation_code(contract, client, provider).await?;
63+
let bytecode = fetch_creation_code_from_etherscan(contract, &etherscan, provider).await?;
6864
let bytecode =
6965
parse_code_output(bytecode, contract, &etherscan, abi_path.as_deref(), true, false)
7066
.await?;

crates/cast/src/cmd/constructor_args.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
use super::{
2-
creation_code::fetch_creation_code,
2+
creation_code::fetch_creation_code_from_etherscan,
33
interface::{fetch_abi_from_etherscan, load_abi_from_file},
44
};
55
use alloy_dyn_abi::DynSolType;
66
use alloy_primitives::{Address, Bytes};
77
use alloy_provider::Provider;
88
use clap::{command, Parser};
99
use eyre::{eyre, OptionExt, Result};
10-
use foundry_block_explorers::Client;
1110
use foundry_cli::{
1211
opts::{EtherscanOpts, RpcOpts},
1312
utils::{self, LoadConfig},
@@ -37,12 +36,10 @@ impl ConstructorArgsArgs {
3736

3837
let config = rpc.load_config()?;
3938
let provider = utils::get_provider(&config)?;
40-
let api_key = etherscan.key().unwrap_or_default();
4139
let chain = provider.get_chain_id().await?;
4240
etherscan.chain = Some(chain.into());
43-
let client = Client::new(chain.into(), api_key)?;
4441

45-
let bytecode = fetch_creation_code(contract, client, provider).await?;
42+
let bytecode = fetch_creation_code_from_etherscan(contract, &etherscan, provider).await?;
4643

4744
let args_arr = parse_constructor_args(bytecode, contract, &etherscan, abi_path).await?;
4845
for arg in args_arr {

crates/cast/src/cmd/creation_code.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,10 @@ impl CreationCodeArgs {
5050

5151
let config = rpc.load_config()?;
5252
let provider = utils::get_provider(&config)?;
53-
let api_key = etherscan.key().unwrap_or_default();
5453
let chain = provider.get_chain_id().await?;
5554
etherscan.chain = Some(chain.into());
56-
let client = Client::new(chain.into(), api_key)?;
5755

58-
let bytecode = fetch_creation_code(contract, client, provider).await?;
56+
let bytecode = fetch_creation_code_from_etherscan(contract, &etherscan, provider).await?;
5957

6058
let bytecode = parse_code_output(
6159
bytecode,
@@ -131,11 +129,16 @@ pub async fn parse_code_output(
131129
}
132130

133131
/// Fetches the creation code of a contract from Etherscan and RPC.
134-
pub async fn fetch_creation_code(
132+
pub async fn fetch_creation_code_from_etherscan(
135133
contract: Address,
136-
client: Client,
134+
etherscan: &EtherscanOpts,
137135
provider: RetryProvider,
138136
) -> Result<Bytes> {
137+
let config = etherscan.load_config()?;
138+
let chain = config.chain.unwrap_or_default();
139+
let api_version = config.get_etherscan_api_version(Some(chain));
140+
let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default();
141+
let client = Client::new_with_api_version(chain, api_key, api_version)?;
139142
let creation_data = client.contract_creation_data(contract).await?;
140143
let creation_tx_hash = creation_data.transaction_hash;
141144
let tx_data = provider.get_transaction_by_hash(creation_tx_hash).await?;

crates/cast/src/cmd/interface.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,9 @@ pub async fn fetch_abi_from_etherscan(
143143
) -> Result<Vec<(JsonAbi, String)>> {
144144
let config = etherscan.load_config()?;
145145
let chain = config.chain.unwrap_or_default();
146+
let api_version = config.get_etherscan_api_version(Some(chain));
146147
let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default();
147-
let client = Client::new(chain, api_key)?;
148+
let client = Client::new_with_api_version(chain, api_key, api_version)?;
148149
let source = client.contract_source_code(address).await?;
149150
source.items.into_iter().map(|item| Ok((item.abi()?, item.contract_name))).collect()
150151
}

crates/cast/src/cmd/run.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,10 @@ impl figment::Provider for RunArgs {
299299
map.insert("etherscan_api_key".into(), api_key.as_str().into());
300300
}
301301

302+
if let Some(api_version) = &self.etherscan.api_version {
303+
map.insert("etherscan_api_version".into(), api_version.to_string().into());
304+
}
305+
302306
if let Some(evm_version) = self.evm_version {
303307
map.insert("evm_version".into(), figment::value::Value::serialize(evm_version)?);
304308
}

crates/cast/src/cmd/storage.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,9 @@ impl StorageArgs {
135135
}
136136

137137
let chain = utils::get_chain(config.chain, &provider).await?;
138+
let api_version = config.get_etherscan_api_version(Some(chain));
138139
let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default();
139-
let client = Client::new(chain, api_key)?;
140+
let client = Client::new_with_api_version(chain, api_key, api_version)?;
140141
let source = if let Some(proxy) = self.proxy {
141142
find_source(client, proxy.resolve(&provider).await?).await?
142143
} else {

crates/cast/src/tx.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use alloy_serde::WithOtherFields;
1313
use alloy_signer::Signer;
1414
use alloy_transport::TransportError;
1515
use eyre::Result;
16+
use foundry_block_explorers::EtherscanApiVersion;
1617
use foundry_cli::{
1718
opts::{CliAuthorizationList, TransactionOpts},
1819
utils::{self, parse_function_args},
@@ -141,6 +142,7 @@ pub struct CastTxBuilder<P, S> {
141142
auth: Option<CliAuthorizationList>,
142143
chain: Chain,
143144
etherscan_api_key: Option<String>,
145+
etherscan_api_version: EtherscanApiVersion,
144146
access_list: Option<Option<AccessList>>,
145147
state: S,
146148
}
@@ -152,6 +154,7 @@ impl<P: Provider<AnyNetwork>> CastTxBuilder<P, InitState> {
152154
let mut tx = WithOtherFields::<TransactionRequest>::default();
153155

154156
let chain = utils::get_chain(config.chain, &provider).await?;
157+
let etherscan_api_version = config.get_etherscan_api_version(Some(chain));
155158
let etherscan_api_key = config.get_etherscan_api_key(Some(chain));
156159
let legacy = tx_opts.legacy || chain.is_legacy();
157160

@@ -192,6 +195,7 @@ impl<P: Provider<AnyNetwork>> CastTxBuilder<P, InitState> {
192195
blob: tx_opts.blob,
193196
chain,
194197
etherscan_api_key,
198+
etherscan_api_version,
195199
auth: tx_opts.auth,
196200
access_list: tx_opts.access_list,
197201
state: InitState,
@@ -208,6 +212,7 @@ impl<P: Provider<AnyNetwork>> CastTxBuilder<P, InitState> {
208212
blob: self.blob,
209213
chain: self.chain,
210214
etherscan_api_key: self.etherscan_api_key,
215+
etherscan_api_version: self.etherscan_api_version,
211216
auth: self.auth,
212217
access_list: self.access_list,
213218
state: ToState { to },
@@ -233,6 +238,7 @@ impl<P: Provider<AnyNetwork>> CastTxBuilder<P, ToState> {
233238
self.chain,
234239
&self.provider,
235240
self.etherscan_api_key.as_deref(),
241+
self.etherscan_api_version,
236242
)
237243
.await?
238244
} else {
@@ -264,6 +270,7 @@ impl<P: Provider<AnyNetwork>> CastTxBuilder<P, ToState> {
264270
blob: self.blob,
265271
chain: self.chain,
266272
etherscan_api_key: self.etherscan_api_key,
273+
etherscan_api_version: self.etherscan_api_version,
267274
auth: self.auth,
268275
access_list: self.access_list,
269276
state: InputState { kind: self.state.to.into(), input, func },

crates/cast/tests/cli/main.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use anvil::{EthereumHardfork, NodeConfig};
99
use foundry_test_utils::{
1010
rpc::{
1111
next_etherscan_api_key, next_http_archive_rpc_url, next_http_rpc_endpoint,
12-
next_mainnet_etherscan_api_key, next_rpc_endpoint, next_ws_rpc_endpoint,
12+
next_rpc_endpoint, next_ws_rpc_endpoint,
1313
},
1414
str,
1515
util::OutputExt,
@@ -1378,7 +1378,7 @@ casttest!(storage_layout_simple, |_prj, cmd| {
13781378
"--block",
13791379
"21034138",
13801380
"--etherscan-api-key",
1381-
next_mainnet_etherscan_api_key().as_str(),
1381+
next_etherscan_api_key().as_str(),
13821382
"0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2",
13831383
])
13841384
.assert_success()
@@ -1405,7 +1405,7 @@ casttest!(storage_layout_simple_json, |_prj, cmd| {
14051405
"--block",
14061406
"21034138",
14071407
"--etherscan-api-key",
1408-
next_mainnet_etherscan_api_key().as_str(),
1408+
next_etherscan_api_key().as_str(),
14091409
"0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2",
14101410
"--json",
14111411
])
@@ -1422,7 +1422,7 @@ casttest!(storage_layout_complex, |_prj, cmd| {
14221422
"--block",
14231423
"21034138",
14241424
"--etherscan-api-key",
1425-
next_mainnet_etherscan_api_key().as_str(),
1425+
next_etherscan_api_key().as_str(),
14261426
"0xBA12222222228d8Ba445958a75a0704d566BF2C8",
14271427
])
14281428
.assert_success()
@@ -1470,7 +1470,7 @@ casttest!(storage_layout_complex_proxy, |_prj, cmd| {
14701470
"--block",
14711471
"7857852",
14721472
"--etherscan-api-key",
1473-
next_mainnet_etherscan_api_key().as_str(),
1473+
next_etherscan_api_key().as_str(),
14741474
"0xE2588A9CAb7Ea877206E35f615a39f84a64A7A3b",
14751475
"--proxy",
14761476
"0x29fcb43b46531bca003ddc8fcb67ffe91900c762"
@@ -1512,7 +1512,7 @@ casttest!(storage_layout_complex_json, |_prj, cmd| {
15121512
"--block",
15131513
"21034138",
15141514
"--etherscan-api-key",
1515-
next_mainnet_etherscan_api_key().as_str(),
1515+
next_etherscan_api_key().as_str(),
15161516
"0xBA12222222228d8Ba445958a75a0704d566BF2C8",
15171517
"--json",
15181518
])
@@ -1601,7 +1601,7 @@ casttest!(fetch_weth_interface_from_etherscan, |_prj, cmd| {
16011601
cmd.args([
16021602
"interface",
16031603
"--etherscan-api-key",
1604-
&next_mainnet_etherscan_api_key(),
1604+
&next_etherscan_api_key(),
16051605
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
16061606
])
16071607
.assert_success()
@@ -1880,7 +1880,7 @@ casttest!(fetch_creation_code_from_etherscan, |_prj, cmd| {
18801880
cmd.args([
18811881
"creation-code",
18821882
"--etherscan-api-key",
1883-
&next_mainnet_etherscan_api_key(),
1883+
&next_etherscan_api_key(),
18841884
"0x0923cad07f06b2d0e5e49e63b8b35738d4156b95",
18851885
"--rpc-url",
18861886
eth_rpc_url.as_str(),
@@ -1899,7 +1899,7 @@ casttest!(fetch_creation_code_only_args_from_etherscan, |_prj, cmd| {
18991899
cmd.args([
19001900
"creation-code",
19011901
"--etherscan-api-key",
1902-
&next_mainnet_etherscan_api_key(),
1902+
&next_etherscan_api_key(),
19031903
"0x6982508145454ce325ddbe47a25d4ec3d2311933",
19041904
"--rpc-url",
19051905
eth_rpc_url.as_str(),
@@ -1919,7 +1919,7 @@ casttest!(fetch_constructor_args_from_etherscan, |_prj, cmd| {
19191919
cmd.args([
19201920
"constructor-args",
19211921
"--etherscan-api-key",
1922-
&next_mainnet_etherscan_api_key(),
1922+
&next_etherscan_api_key(),
19231923
"0x6982508145454ce325ddbe47a25d4ec3d2311933",
19241924
"--rpc-url",
19251925
eth_rpc_url.as_str(),
@@ -1940,7 +1940,7 @@ casttest!(test_non_mainnet_traces, |prj, cmd| {
19401940
"--rpc-url",
19411941
next_rpc_endpoint(NamedChain::Optimism).as_str(),
19421942
"--etherscan-api-key",
1943-
next_etherscan_api_key(NamedChain::Optimism).as_str(),
1943+
next_etherscan_api_key().as_str(),
19441944
])
19451945
.assert_success()
19461946
.stdout_eq(str![[r#"
@@ -1963,7 +1963,7 @@ casttest!(fetch_artifact_from_etherscan, |_prj, cmd| {
19631963
cmd.args([
19641964
"artifact",
19651965
"--etherscan-api-key",
1966-
&next_mainnet_etherscan_api_key(),
1966+
&next_etherscan_api_key(),
19671967
"0x0923cad07f06b2d0e5e49e63b8b35738d4156b95",
19681968
"--rpc-url",
19691969
eth_rpc_url.as_str(),
@@ -2444,7 +2444,7 @@ contract WETH9 {
24442444

24452445
casttest!(fetch_src_default, |_prj, cmd| {
24462446
let weth = address!("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
2447-
let etherscan_api_key = next_mainnet_etherscan_api_key();
2447+
let etherscan_api_key = next_etherscan_api_key();
24482448

24492449
cmd.args(["source", &weth.to_string(), "--flatten", "--etherscan-api-key", &etherscan_api_key])
24502450
.assert_success()

crates/cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ foundry-evm.workspace = true
2121
foundry-wallets.workspace = true
2222

2323
foundry-compilers = { workspace = true, features = ["full"] }
24+
foundry-block-explorers.workspace = true
2425

2526
alloy-eips.workspace = true
2627
alloy-dyn-abi.workspace = true

crates/cli/src/opts/rpc.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::opts::ChainValueParser;
22
use alloy_chains::ChainKind;
33
use clap::Parser;
44
use eyre::Result;
5+
use foundry_block_explorers::EtherscanApiVersion;
56
use foundry_config::{
67
figment::{
78
self,
@@ -114,6 +115,16 @@ pub struct EtherscanOpts {
114115
#[serde(rename = "etherscan_api_key", skip_serializing_if = "Option::is_none")]
115116
pub key: Option<String>,
116117

118+
/// The Etherscan API version.
119+
#[arg(
120+
short,
121+
long = "etherscan-api-version",
122+
alias = "api-version",
123+
env = "ETHERSCAN_API_VERSION"
124+
)]
125+
#[serde(rename = "etherscan_api_version", skip_serializing_if = "Option::is_none")]
126+
pub api_version: Option<EtherscanApiVersion>,
127+
117128
/// The chain name or EIP-155 chain ID.
118129
#[arg(
119130
short,
@@ -154,6 +165,11 @@ impl EtherscanOpts {
154165
if let Some(key) = self.key() {
155166
dict.insert("etherscan_api_key".into(), key.into());
156167
}
168+
169+
if let Some(api_version) = &self.api_version {
170+
dict.insert("etherscan_api_version".into(), api_version.to_string().into());
171+
}
172+
157173
if let Some(chain) = self.chain {
158174
if let ChainKind::Id(id) = chain.kind() {
159175
dict.insert("chain_id".into(), (*id).into());

0 commit comments

Comments
 (0)