From 4e981855d7f4c77be1ce09af87d1eff3c67a372b Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 1 Feb 2024 12:50:21 +0100 Subject: [PATCH] fix: fix etherscan api parsing again (#6984) --- crates/cast/bin/cmd/storage.rs | 20 ++++++++++++++++++++ crates/config/src/etherscan.rs | 30 ++++++++++++++++++++++++++++++ crates/config/src/lib.rs | 33 ++++++++++++++++++++++++--------- 3 files changed, 74 insertions(+), 9 deletions(-) diff --git a/crates/cast/bin/cmd/storage.rs b/crates/cast/bin/cmd/storage.rs index b580d9a9d8352..eb5b7cd4323f4 100644 --- a/crates/cast/bin/cmd/storage.rs +++ b/crates/cast/bin/cmd/storage.rs @@ -288,3 +288,23 @@ fn is_storage_layout_empty(storage_layout: &Option) -> bool { true } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_storage_etherscan_api_key() { + let args = + StorageArgs::parse_from(["foundry-cli", "addr", "--etherscan-api-key", "dummykey"]); + assert_eq!(args.etherscan.key(), Some("dummykey".to_string())); + + std::env::set_var("ETHERSCAN_API_KEY", "FXY"); + let config = Config::from(&args); + std::env::remove_var("ETHERSCAN_API_KEY"); + assert_eq!(config.etherscan_api_key, Some("dummykey".to_string())); + + let key = config.get_etherscan_api_key(None).unwrap(); + assert_eq!(key, "dummykey".to_string()); + } +} diff --git a/crates/config/src/etherscan.rs b/crates/config/src/etherscan.rs index 5860fd7598e82..6e2030cb968b0 100644 --- a/crates/config/src/etherscan.rs +++ b/crates/config/src/etherscan.rs @@ -4,6 +4,11 @@ use crate::{ resolve::{interpolate, UnresolvedEnvVarError, RE_PLACEHOLDER}, Chain, Config, NamedChain, }; +use figment::{ + providers::Env, + value::{Dict, Map}, + Error, Metadata, Profile, Provider, +}; use inflector::Inflector; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{ @@ -16,6 +21,31 @@ use std::{ /// The user agent to use when querying the etherscan API. pub const ETHERSCAN_USER_AGENT: &str = concat!("foundry/", env!("CARGO_PKG_VERSION")); +/// A [Provider] that provides Etherscan API key from the environment if it's not empty. +/// +/// This prevents `ETHERSCAN_API_KEY=""` if it's set but empty +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[non_exhaustive] +pub(crate) struct EtherscanEnvProvider; + +impl Provider for EtherscanEnvProvider { + fn metadata(&self) -> Metadata { + Env::raw().metadata() + } + + fn data(&self) -> Result, Error> { + let mut dict = Dict::default(); + let env_provider = Env::raw().only(&["ETHERSCAN_API_KEY"]); + if let Some((key, value)) = env_provider.iter().next() { + if !value.trim().is_empty() { + dict.insert(key.as_str().to_string(), value.into()); + } + } + + Ok(Map::from([(Config::selected_profile(), dict)])) + } +} + /// Errors that can occur when creating an `EtherscanConfig` #[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)] pub enum EtherscanConfigError { diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index f271f6d9a6e1c..d40988a91260d 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -95,6 +95,7 @@ pub use invariant::InvariantConfig; use providers::remappings::RemappingsProvider; mod inline; +use crate::etherscan::EtherscanEnvProvider; pub use inline::{validate_profiles, InlineConfig, InlineConfigError, InlineConfigParser, NatSpec}; /// Foundry configuration @@ -1589,6 +1590,7 @@ impl From for Figment { .global(), ) .merge(DappEnvCompatProvider) + .merge(EtherscanEnvProvider::default()) .merge( Env::prefixed("FOUNDRY_") .ignore(&["PROFILE", "REMAPPINGS", "LIBRARIES", "FFI", "FS_PERMISSIONS"]) @@ -1606,15 +1608,6 @@ impl From for Figment { ) .select(profile.clone()); - // Ensure only non empty etherscan var is merged - // This prevents `ETHERSCAN_API_KEY=""` if it's set but empty - let env_provider = Env::raw().only(&["ETHERSCAN_API_KEY"]); - if let Some((key, value)) = env_provider.iter().next() { - if !value.trim().is_empty() { - figment = figment.merge((key.as_str(), value)); - } - } - // we try to merge remappings after we've merged all other providers, this prevents // redundant fs lookups to determine the default remappings that are eventually updated by // other providers, like the toml file @@ -4368,6 +4361,28 @@ mod tests { }); } + #[test] + fn test_etherscan_api_key_figment() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r" + [default] + etherscan_api_key = 'DUMMY' + ", + )?; + jail.set_env("ETHERSCAN_API_KEY", "ETHER"); + + let figment = Config::figment_with_root(jail.directory()) + .merge(("etherscan_api_key", "USER_KEY")); + + let loaded = Config::from_provider(figment); + assert_eq!(loaded.etherscan_api_key, Some("USER_KEY".into())); + + Ok(()) + }); + } + // a test to print the config, mainly used to update the example config in the README #[test] #[ignore]