Skip to content

Commit f60a1d8

Browse files
fix(forge): custom verifiers should not throw on unknown etherscan chains (#11442)
* fix(forge): check if supported chain in verifier client * Apply suggestions from code review * heck if verifier url passed if not etherscan * Tests --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com>
1 parent 3f7885a commit f60a1d8

File tree

4 files changed

+85
-19
lines changed

4 files changed

+85
-19
lines changed

crates/config/src/lib.rs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,8 @@ pub use endpoints::{
6262
};
6363

6464
mod etherscan;
65-
use etherscan::{
66-
EtherscanConfigError, EtherscanConfigs, EtherscanEnvProvider, ResolvedEtherscanConfig,
67-
};
65+
pub use etherscan::EtherscanConfigError;
66+
use etherscan::{EtherscanConfigs, EtherscanEnvProvider, ResolvedEtherscanConfig};
6867

6968
pub mod resolve;
7069
pub use resolve::UnresolvedEnvVarError;
@@ -1443,15 +1442,10 @@ impl Config {
14431442

14441443
// etherscan fallback via API key
14451444
if let Some(key) = self.etherscan_api_key.as_ref() {
1446-
match ResolvedEtherscanConfig::create(key, chain.or(self.chain).unwrap_or_default()) {
1447-
Some(config) => return Ok(Some(config)),
1448-
None => {
1449-
return Err(EtherscanConfigError::UnknownChain(
1450-
String::new(),
1451-
chain.unwrap_or_default(),
1452-
));
1453-
}
1454-
}
1445+
return Ok(ResolvedEtherscanConfig::create(
1446+
key,
1447+
chain.or(self.chain).unwrap_or_default(),
1448+
));
14551449
}
14561450
Ok(None)
14571451
}

crates/forge/tests/cli/verify.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,3 +314,49 @@ forgetest!(can_guess_constructor_args, |prj, cmd| {
314314
forgetest!(can_verify_random_contract_sepolia_default_sourcify, |prj, cmd| {
315315
verify_on_chain(EnvExternalities::sepolia_empty_verifier(), prj, cmd);
316316
});
317+
318+
// Tests that verify properly validates verifier arguments.
319+
// <https://github.com/foundry-rs/foundry/issues/11430>
320+
forgetest_init!(can_validate_verifier_settings, |prj, cmd| {
321+
// No verifier URL.
322+
cmd.args([
323+
"verify-contract",
324+
"--rpc-url",
325+
"https://rpc.sepolia-api.lisk.com",
326+
"--verifier",
327+
"blockscout",
328+
"0x19b248616E4964f43F611b5871CE1250f360E9d3",
329+
"src/Counter.sol:Counter",
330+
])
331+
.assert_failure()
332+
.stderr_eq(str![[r#"
333+
Error: No verifier URL specified for verifier blockscout
334+
335+
"#]]);
336+
337+
// Unknown Etherscan chain.
338+
cmd.forge_fuse()
339+
.args([
340+
"verify-contract",
341+
"--rpc-url",
342+
"https://rpc.sepolia-api.lisk.com",
343+
"--verifier",
344+
"etherscan",
345+
"0x19b248616E4964f43F611b5871CE1250f360E9d3",
346+
"src/Counter.sol:Counter",
347+
])
348+
.assert_failure()
349+
.stderr_eq(str![[r#"
350+
Error: No known Etherscan API URL for chain `4202`. To fix this, please:
351+
1. Specify a `url`
352+
2. Verify the chain `4202` is correct
353+
354+
"#]]);
355+
356+
cmd.forge_fuse().args(["verify-contract", "--rpc-url", "https://rpc.sepolia-api.lisk.com", "--verifier", "blockscout", "--verifier-url", "https://sepolia-blockscout.lisk.com/api", "0x19b248616E4964f43F611b5871CE1250f360E9d3", "src/Counter.sol:Counter"]).assert_success().stdout_eq(str![[r#"
357+
Start verifying contract `0x19b248616E4964f43F611b5871CE1250f360E9d3` deployed on 4202
358+
359+
Contract [src/Counter.sol:Counter] "0x19b248616E4964f43F611b5871CE1250f360E9d3" is already verified. Skipping verification.
360+
361+
"#]]);
362+
});

crates/verify/src/provider.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use foundry_compilers::{
1414
multi::{MultiCompilerParser, MultiCompilerSettings},
1515
solc::Solc,
1616
};
17-
use foundry_config::Config;
17+
use foundry_config::{Chain, Config, EtherscanConfigError};
1818
use semver::Version;
1919
use std::{fmt, path::PathBuf, str::FromStr};
2020

@@ -168,7 +168,12 @@ pub enum VerificationProviderType {
168168

169169
impl VerificationProviderType {
170170
/// Returns the corresponding `VerificationProvider` for the key
171-
pub fn client(&self, key: Option<&str>) -> Result<Box<dyn VerificationProvider>> {
171+
pub fn client(
172+
&self,
173+
key: Option<&str>,
174+
chain: Option<Chain>,
175+
has_url: bool,
176+
) -> Result<Box<dyn VerificationProvider>> {
172177
let has_key = key.as_ref().is_some_and(|k| !k.is_empty());
173178
// 1. If no verifier or `--verifier sourcify` is set and no API key provided, use Sourcify.
174179
if !has_key && self.is_sourcify() {
@@ -179,17 +184,26 @@ impl VerificationProviderType {
179184
return Ok(Box::<SourcifyVerificationProvider>::default());
180185
}
181186

182-
// 2. If `--verifier etherscan` is explicitly set, enforce the API key requirement.
187+
// 2. If `--verifier etherscan` is explicitly set, check if chain is supported and
188+
// enforce the API key requirement.
183189
if self.is_etherscan() {
190+
if let Some(chain) = chain
191+
&& chain.etherscan_urls().is_none()
192+
{
193+
eyre::bail!(EtherscanConfigError::UnknownChain(String::new(), chain))
194+
}
184195
if !has_key {
185196
eyre::bail!("ETHERSCAN_API_KEY must be set to use Etherscan as a verifier")
186197
}
187198
return Ok(Box::<EtherscanVerificationProvider>::default());
188199
}
189200

190201
// 3. If `--verifier blockscout | oklink | custom` is explicitly set, use the chosen
191-
// verifier.
202+
// verifier and make sure an URL was specified.
192203
if matches!(self, Self::Blockscout | Self::Oklink | Self::Custom) {
204+
if !has_url {
205+
eyre::bail!("No verifier URL specified for verifier {}", self);
206+
}
193207
return Ok(Box::<EtherscanVerificationProvider>::default());
194208
}
195209

crates/verify/src/verify.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ impl VerifyArgs {
253253
{
254254
sh_println!("Constructor args: {args}")?
255255
}
256-
self.verifier.verifier.client(self.etherscan.key().as_deref())?.verify(self, context).await.map_err(|err| {
256+
self.verifier.verifier.client(self.etherscan.key().as_deref(), self.etherscan.chain, self.verifier.verifier_url.is_some())?.verify(self, context).await.map_err(|err| {
257257
if let Some(verifier_url) = verifier_url {
258258
match Url::parse(&verifier_url) {
259259
Ok(url) => {
@@ -277,7 +277,11 @@ impl VerifyArgs {
277277

278278
/// Returns the configured verification provider
279279
pub fn verification_provider(&self) -> Result<Box<dyn VerificationProvider>> {
280-
self.verifier.verifier.client(self.etherscan.key().as_deref())
280+
self.verifier.verifier.client(
281+
self.etherscan.key().as_deref(),
282+
self.etherscan.chain,
283+
self.verifier.verifier_url.is_some(),
284+
)
281285
}
282286

283287
/// Resolves [VerificationContext] object either from entered contract name or by trying to
@@ -476,7 +480,15 @@ impl VerifyCheckArgs {
476480
"Checking verification status on {}",
477481
self.etherscan.chain.unwrap_or_default()
478482
)?;
479-
self.verifier.verifier.client(self.etherscan.key().as_deref())?.check(self).await
483+
self.verifier
484+
.verifier
485+
.client(
486+
self.etherscan.key().as_deref(),
487+
self.etherscan.chain,
488+
self.verifier.verifier_url.is_some(),
489+
)?
490+
.check(self)
491+
.await
480492
}
481493
}
482494

0 commit comments

Comments
 (0)