Skip to content

Commit ac68cb6

Browse files
iainnashmattsse
andauthored
Add v2 verify routes (#73)
* Fix existing verify integration tests * Add `standard-json-input` integration tests * Fix current single file test * Add v2 integration tests * Allow v2 setting on client with etherscan client This is a prerequisite PR for foundry-rs/foundry#9196 **Notes:** * Fantomscan test has no env var passed in and can be removed safely. * Gas estimates now do not fail with even 1 passed in, so skipping the failure test because I couldn't find a failure test. --------- Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
1 parent 5bdc893 commit ac68cb6

File tree

9 files changed

+657
-32
lines changed

9 files changed

+657
-32
lines changed

crates/block-explorers/src/lib.rs

+60-4
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,27 @@ pub mod verify;
4444

4545
pub(crate) type Result<T, E = EtherscanError> = std::result::Result<T, E>;
4646

47+
/// The URL for the etherscan V2 API without the chainid param set.
48+
pub const ETHERSCAN_V2_API_BASE_URL: &str = "https://api.etherscan.io/v2/api?chainid=";
49+
50+
/// The Etherscan.io API version 1 - classic verifier, one API per chain, 2 - new multichain
51+
/// verifier
52+
#[derive(Clone, Default, Debug, PartialEq, Copy)]
53+
pub enum EtherscanApiVersion {
54+
#[default]
55+
V1,
56+
V2,
57+
}
58+
4759
/// The Etherscan.io API client.
4860
#[derive(Clone, Debug)]
4961
pub struct Client {
5062
/// Client that executes HTTP requests
5163
client: reqwest::Client,
5264
/// Etherscan API key
5365
api_key: Option<String>,
66+
/// Etherscan API version
67+
etherscan_api_version: EtherscanApiVersion,
5468
/// Etherscan API endpoint like <https://api(-chain).etherscan.io/api>
5569
etherscan_api_url: Url,
5670
/// Etherscan base endpoint like <https://etherscan.io>
@@ -99,6 +113,16 @@ impl Client {
99113
Client::builder().with_api_key(api_key).chain(chain)?.build()
100114
}
101115

116+
/// Create a new client with the correct endpoint with the chain and chosen API v2 version
117+
pub fn new_v2_from_env(chain: Chain) -> Result<Self> {
118+
let api_key = std::env::var("ETHERSCAN_API_KEY")?;
119+
Client::builder()
120+
.with_api_version(EtherscanApiVersion::V2)
121+
.with_api_key(api_key)
122+
.chain(chain)?
123+
.build()
124+
}
125+
102126
/// Create a new client with the correct endpoints based on the chain and API key
103127
/// from the default environment variable defined in [`Chain`].
104128
pub fn new_from_env(chain: Chain) -> Result<Self> {
@@ -155,6 +179,11 @@ impl Client {
155179
self
156180
}
157181

182+
/// Returns the configured etherscan api version.
183+
pub fn etherscan_api_version(&self) -> &EtherscanApiVersion {
184+
&self.etherscan_api_version
185+
}
186+
158187
pub fn etherscan_api_url(&self) -> &Url {
159188
&self.etherscan_api_url
160189
}
@@ -279,6 +308,8 @@ pub struct ClientBuilder {
279308
api_key: Option<String>,
280309
/// Etherscan API endpoint like <https://api(-chain).etherscan.io/api>
281310
etherscan_api_url: Option<Url>,
311+
/// Etherscan API version (v2 is new verifier version, v1 is the default)
312+
etherscan_api_version: EtherscanApiVersion,
282313
/// Etherscan base endpoint like <https://etherscan.io>
283314
etherscan_url: Option<Url>,
284315
/// Path to where ABI files should be cached
@@ -303,14 +334,29 @@ impl ClientBuilder {
303334
) -> (reqwest::Result<Url>, reqwest::Result<Url>) {
304335
(api.into_url(), url.into_url())
305336
}
306-
let (etherscan_api_url, etherscan_url) = chain
337+
let (default_etherscan_api_url, etherscan_url) = chain
307338
.named()
308339
.ok_or_else(|| EtherscanError::ChainNotSupported(chain))?
309340
.etherscan_urls()
310341
.map(urls)
311342
.ok_or_else(|| EtherscanError::ChainNotSupported(chain))?;
312343

313-
self.with_chain_id(chain).with_api_url(etherscan_api_url?)?.with_url(etherscan_url?)
344+
// V2 etherscan default API urls are different – this handles that case.
345+
let etherscan_api_url = if self.etherscan_api_version == EtherscanApiVersion::V2 {
346+
let chain_id = chain.id();
347+
Url::parse(&format!("{ETHERSCAN_V2_API_BASE_URL}{chain_id}"))
348+
.map_err(|_| EtherscanError::Builder("Bad URL Parse".into()))?
349+
} else {
350+
default_etherscan_api_url?
351+
};
352+
353+
self.with_chain_id(chain).with_api_url(etherscan_api_url)?.with_url(etherscan_url?)
354+
}
355+
356+
/// Configures the etherscan api version
357+
pub fn with_api_version(mut self, api_version: EtherscanApiVersion) -> Self {
358+
self.etherscan_api_version = api_version;
359+
self
314360
}
315361

316362
/// Configures the etherscan url
@@ -370,14 +416,24 @@ impl ClientBuilder {
370416
/// - `etherscan_api_url`
371417
/// - `etherscan_url`
372418
pub fn build(self) -> Result<Client> {
373-
let ClientBuilder { client, api_key, etherscan_api_url, etherscan_url, cache, chain_id } =
374-
self;
419+
let ClientBuilder {
420+
client,
421+
api_key,
422+
etherscan_api_version,
423+
etherscan_api_url,
424+
etherscan_url,
425+
cache,
426+
chain_id,
427+
} = self;
375428

376429
let client = Client {
377430
client: client.unwrap_or_default(),
378431
api_key,
379432
etherscan_api_url: etherscan_api_url
433+
.clone()
380434
.ok_or_else(|| EtherscanError::Builder("etherscan api url".to_string()))?,
435+
// Set default API version to V1 if missing
436+
etherscan_api_version,
381437
etherscan_url: etherscan_url
382438
.ok_or_else(|| EtherscanError::Builder("etherscan url".to_string()))?,
383439
cache,
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"expiry":1741879991,"data":[{"type":"constructor","inputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"deposit","inputs":[{"name":"pubkey","type":"bytes","internalType":"bytes"},{"name":"withdrawal_credentials","type":"bytes","internalType":"bytes"},{"name":"signature","type":"bytes","internalType":"bytes"},{"name":"deposit_data_root","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"get_deposit_count","inputs":[],"outputs":[{"name":"","type":"bytes","internalType":"bytes"}],"stateMutability":"view"},{"type":"function","name":"get_deposit_root","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"supportsInterface","inputs":[{"name":"interfaceId","type":"bytes4","internalType":"bytes4"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"event","name":"DepositEvent","inputs":[{"name":"pubkey","type":"bytes","indexed":false,"internalType":"bytes"},{"name":"withdrawal_credentials","type":"bytes","indexed":false,"internalType":"bytes"},{"name":"amount","type":"bytes","indexed":false,"internalType":"bytes"},{"name":"signature","type":"bytes","indexed":false,"internalType":"bytes"},{"name":"index","type":"bytes","indexed":false,"internalType":"bytes"}],"anonymous":false}]}
1+
{"expiry":1742116310,"data":[{"type":"constructor","inputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"deposit","inputs":[{"name":"pubkey","type":"bytes","internalType":"bytes"},{"name":"withdrawal_credentials","type":"bytes","internalType":"bytes"},{"name":"signature","type":"bytes","internalType":"bytes"},{"name":"deposit_data_root","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"get_deposit_count","inputs":[],"outputs":[{"name":"","type":"bytes","internalType":"bytes"}],"stateMutability":"view"},{"type":"function","name":"get_deposit_root","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"supportsInterface","inputs":[{"name":"interfaceId","type":"bytes4","internalType":"bytes4"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"type":"event","name":"DepositEvent","inputs":[{"name":"pubkey","type":"bytes","indexed":false,"internalType":"bytes"},{"name":"withdrawal_credentials","type":"bytes","indexed":false,"internalType":"bytes"},{"name":"amount","type":"bytes","indexed":false,"internalType":"bytes"},{"name":"signature","type":"bytes","indexed":false,"internalType":"bytes"},{"name":"index","type":"bytes","indexed":false,"internalType":"bytes"}],"anonymous":false}]}

crates/block-explorers/test-data/cache/sources/0x00000000219ab540356cbb839cbe05303d7705fa.json

+1-1
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.17;
3+
4+
/// @title IProtocolRewards
5+
/// @notice The interface for deposits & withdrawals for Protocol Rewards
6+
interface IProtocolRewards {
7+
/// @notice Rewards Deposit Event
8+
/// @param creator Creator for NFT rewards
9+
/// @param createReferral Creator referral
10+
/// @param mintReferral Mint referral user
11+
/// @param firstMinter First minter reward recipient
12+
/// @param zora ZORA recipient
13+
/// @param from The caller of the deposit
14+
/// @param creatorReward Creator reward amount
15+
/// @param createReferralReward Creator referral reward
16+
/// @param mintReferralReward Mint referral amount
17+
/// @param firstMinterReward First minter reward amount
18+
/// @param zoraReward ZORA amount
19+
event RewardsDeposit(
20+
address indexed creator,
21+
address indexed createReferral,
22+
address indexed mintReferral,
23+
address firstMinter,
24+
address zora,
25+
address from,
26+
uint256 creatorReward,
27+
uint256 createReferralReward,
28+
uint256 mintReferralReward,
29+
uint256 firstMinterReward,
30+
uint256 zoraReward
31+
);
32+
33+
/// @notice Deposit Event
34+
/// @param from From user
35+
/// @param to To user (within contract)
36+
/// @param reason Optional bytes4 reason for indexing
37+
/// @param amount Amount of deposit
38+
/// @param comment Optional user comment
39+
event Deposit(address indexed from, address indexed to, bytes4 indexed reason, uint256 amount, string comment);
40+
41+
/// @notice Withdraw Event
42+
/// @param from From user
43+
/// @param to To user (within contract)
44+
/// @param amount Amount of deposit
45+
event Withdraw(address indexed from, address indexed to, uint256 amount);
46+
47+
/// @notice Cannot send to address zero
48+
error ADDRESS_ZERO();
49+
50+
/// @notice Function argument array length mismatch
51+
error ARRAY_LENGTH_MISMATCH();
52+
53+
/// @notice Invalid deposit
54+
error INVALID_DEPOSIT();
55+
56+
/// @notice Invalid signature for deposit
57+
error INVALID_SIGNATURE();
58+
59+
/// @notice Invalid withdraw
60+
error INVALID_WITHDRAW();
61+
62+
/// @notice Signature for withdraw is too old and has expired
63+
error SIGNATURE_DEADLINE_EXPIRED();
64+
65+
/// @notice Low-level ETH transfer has failed
66+
error TRANSFER_FAILED();
67+
68+
/// @notice Generic function to deposit ETH for a recipient, with an optional comment
69+
/// @param to Address to deposit to
70+
/// @param to Reason system reason for deposit (used for indexing)
71+
/// @param comment Optional comment as reason for deposit
72+
function deposit(address to, bytes4 why, string calldata comment) external payable;
73+
74+
/// @notice Generic function to deposit ETH for multiple recipients, with an optional comment
75+
/// @param recipients recipients to send the amount to, array aligns with amounts
76+
/// @param amounts amounts to send to each recipient, array aligns with recipients
77+
/// @param reasons optional bytes4 hash for indexing
78+
/// @param comment Optional comment to include with mint
79+
function depositBatch(address[] calldata recipients, uint256[] calldata amounts, bytes4[] calldata reasons, string calldata comment) external payable;
80+
81+
/// @notice Used by Zora ERC-721 & ERC-1155 contracts to deposit protocol rewards
82+
/// @param creator Creator for NFT rewards
83+
/// @param creatorReward Creator reward amount
84+
/// @param createReferral Creator referral
85+
/// @param createReferralReward Creator referral reward
86+
/// @param mintReferral Mint referral user
87+
/// @param mintReferralReward Mint referral amount
88+
/// @param firstMinter First minter reward
89+
/// @param firstMinterReward First minter reward amount
90+
/// @param zora ZORA recipient
91+
/// @param zoraReward ZORA amount
92+
function depositRewards(
93+
address creator,
94+
uint256 creatorReward,
95+
address createReferral,
96+
uint256 createReferralReward,
97+
address mintReferral,
98+
uint256 mintReferralReward,
99+
address firstMinter,
100+
uint256 firstMinterReward,
101+
address zora,
102+
uint256 zoraReward
103+
) external payable;
104+
105+
/// @notice Withdraw protocol rewards
106+
/// @param to Withdraws from msg.sender to this address
107+
/// @param amount amount to withdraw
108+
function withdraw(address to, uint256 amount) external;
109+
}

0 commit comments

Comments
 (0)