forge_verify/
provider.rs

1use crate::{
2    etherscan::EtherscanVerificationProvider,
3    sourcify::SourcifyVerificationProvider,
4    verify::{VerifyArgs, VerifyCheckArgs},
5};
6use alloy_json_abi::JsonAbi;
7use async_trait::async_trait;
8use eyre::{OptionExt, Result};
9use foundry_common::compile::ProjectCompiler;
10use foundry_compilers::{
11    Graph, Project,
12    artifacts::{Metadata, Source, output_selection::OutputSelection},
13    compilers::solc::SolcCompiler,
14    multi::{MultiCompilerParser, MultiCompilerSettings},
15    solc::Solc,
16};
17use foundry_config::{Chain, Config, EtherscanConfigError};
18use semver::Version;
19use std::{fmt, path::PathBuf, str::FromStr};
20
21/// Container with data required for contract verification.
22#[derive(Debug, Clone)]
23pub struct VerificationContext {
24    pub config: Config,
25    pub project: Project,
26    pub target_path: PathBuf,
27    pub target_name: String,
28    pub compiler_version: Version,
29    pub compiler_settings: MultiCompilerSettings,
30}
31
32impl VerificationContext {
33    pub fn new(
34        target_path: PathBuf,
35        target_name: String,
36        compiler_version: Version,
37        config: Config,
38        compiler_settings: MultiCompilerSettings,
39    ) -> Result<Self> {
40        let mut project = config.project()?;
41        project.no_artifacts = true;
42
43        let solc = Solc::find_or_install(&compiler_version)?;
44        project.compiler.solc = Some(SolcCompiler::Specific(solc));
45
46        Ok(Self { config, project, target_name, target_path, compiler_version, compiler_settings })
47    }
48
49    /// Compiles target contract requesting only ABI and returns it.
50    pub fn get_target_abi(&self) -> Result<JsonAbi> {
51        let mut project = self.project.clone();
52        project.update_output_selection(|selection| {
53            *selection = OutputSelection::common_output_selection(["abi".to_string()])
54        });
55
56        let output = ProjectCompiler::new()
57            .quiet(true)
58            .files([self.target_path.clone()])
59            .compile(&project)?;
60
61        let artifact = output
62            .find(&self.target_path, &self.target_name)
63            .ok_or_eyre("failed to find target artifact when compiling for abi")?;
64
65        artifact.abi.clone().ok_or_eyre("target artifact does not have an ABI")
66    }
67
68    /// Compiles target file requesting only metadata and returns it.
69    pub fn get_target_metadata(&self) -> Result<Metadata> {
70        let mut project = self.project.clone();
71        project.update_output_selection(|selection| {
72            *selection = OutputSelection::common_output_selection(["metadata".to_string()]);
73        });
74
75        let output = ProjectCompiler::new()
76            .quiet(true)
77            .files([self.target_path.clone()])
78            .compile(&project)?;
79
80        let artifact = output
81            .find(&self.target_path, &self.target_name)
82            .ok_or_eyre("failed to find target artifact when compiling for metadata")?;
83
84        artifact.metadata.clone().ok_or_eyre("target artifact does not have an ABI")
85    }
86
87    /// Returns [Vec] containing imports of the target file.
88    pub fn get_target_imports(&self) -> Result<Vec<PathBuf>> {
89        let mut sources = self.project.paths.read_input_files()?;
90        sources.insert(self.target_path.clone(), Source::read(&self.target_path)?);
91        let graph = Graph::<MultiCompilerParser>::resolve_sources(&self.project.paths, sources)?;
92
93        Ok(graph.imports(&self.target_path).into_iter().map(Into::into).collect())
94    }
95}
96
97/// An abstraction for various verification providers such as etherscan, sourcify, blockscout
98#[async_trait]
99pub trait VerificationProvider {
100    /// This should ensure the verify request can be prepared successfully.
101    ///
102    /// Caution: Implementers must ensure that this _never_ sends the actual verify request
103    /// `[VerificationProvider::verify]`, instead this is supposed to evaluate whether the given
104    /// [`VerifyArgs`] are valid to begin with. This should prevent situations where there's a
105    /// contract deployment that's executed before the verify request and the subsequent verify task
106    /// fails due to misconfiguration.
107    async fn preflight_verify_check(
108        &mut self,
109        args: VerifyArgs,
110        context: VerificationContext,
111    ) -> Result<()>;
112
113    /// Sends the actual verify request for the targeted contract.
114    async fn verify(&mut self, args: VerifyArgs, context: VerificationContext) -> Result<()>;
115
116    /// Checks whether the contract is verified.
117    async fn check(&self, args: VerifyCheckArgs) -> Result<()>;
118}
119
120impl FromStr for VerificationProviderType {
121    type Err = String;
122
123    fn from_str(s: &str) -> Result<Self, Self::Err> {
124        match s {
125            "e" | "etherscan" => Ok(Self::Etherscan),
126            "s" | "sourcify" => Ok(Self::Sourcify),
127            "b" | "blockscout" => Ok(Self::Blockscout),
128            "o" | "oklink" => Ok(Self::Oklink),
129            "c" | "custom" => Ok(Self::Custom),
130            _ => Err(format!("Unknown provider: {s}")),
131        }
132    }
133}
134
135impl fmt::Display for VerificationProviderType {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        match self {
138            Self::Etherscan => {
139                write!(f, "etherscan")?;
140            }
141            Self::Sourcify => {
142                write!(f, "sourcify")?;
143            }
144            Self::Blockscout => {
145                write!(f, "blockscout")?;
146            }
147            Self::Oklink => {
148                write!(f, "oklink")?;
149            }
150            Self::Custom => {
151                write!(f, "custom")?;
152            }
153        };
154        Ok(())
155    }
156}
157
158#[derive(Clone, Debug, Default, PartialEq, Eq, clap::ValueEnum)]
159pub enum VerificationProviderType {
160    Etherscan,
161    #[default]
162    Sourcify,
163    Blockscout,
164    Oklink,
165    /// Custom verification provider, requires compatibility with the Etherscan API.
166    Custom,
167}
168
169impl VerificationProviderType {
170    /// Returns the corresponding `VerificationProvider` for the key
171    pub fn client(
172        &self,
173        key: Option<&str>,
174        chain: Option<Chain>,
175        has_url: bool,
176    ) -> Result<Box<dyn VerificationProvider>> {
177        let has_key = key.as_ref().is_some_and(|k| !k.is_empty());
178        // 1. If no verifier or `--verifier sourcify` is set and no API key provided, use Sourcify.
179        if !has_key && self.is_sourcify() {
180            sh_println!(
181                "Attempting to verify on Sourcify. Pass the --etherscan-api-key <API_KEY> to verify on Etherscan, \
182            or use the --verifier flag to verify on another provider."
183            )?;
184            return Ok(Box::<SourcifyVerificationProvider>::default());
185        }
186
187        // 2. If `--verifier etherscan` is explicitly set, check if chain is supported and
188        // enforce the API key requirement.
189        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            }
195            if !has_key {
196                eyre::bail!("ETHERSCAN_API_KEY must be set to use Etherscan as a verifier")
197            }
198            return Ok(Box::<EtherscanVerificationProvider>::default());
199        }
200
201        // 3. If `--verifier blockscout | oklink | custom` is explicitly set, use the chosen
202        //    verifier and make sure an URL was specified.
203        if matches!(self, Self::Blockscout | Self::Oklink | Self::Custom) {
204            if !has_url {
205                eyre::bail!("No verifier URL specified for verifier {}", self);
206            }
207            return Ok(Box::<EtherscanVerificationProvider>::default());
208        }
209
210        // 4. If no `--verifier` is specified but `ETHERSCAN_API_KEY` is set, default to Etherscan.
211        if has_key {
212            return Ok(Box::<EtherscanVerificationProvider>::default());
213        }
214
215        // 5. If no valid provider is specified, bail.
216        eyre::bail!(
217            "No valid verification provider specified. Pass the --verifier flag to specify a provider or set the ETHERSCAN_API_KEY environment variable to use Etherscan as a verifier."
218        )
219    }
220
221    pub fn is_sourcify(&self) -> bool {
222        matches!(self, Self::Sourcify)
223    }
224
225    pub fn is_etherscan(&self) -> bool {
226        matches!(self, Self::Etherscan)
227    }
228}