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    artifacts::{output_selection::OutputSelection, Metadata, Source},
12    compilers::{multi::MultiCompilerParsedSource, solc::SolcCompiler},
13    multi::MultiCompilerSettings,
14    solc::Solc,
15    Graph, Project,
16};
17use foundry_config::Config;
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 =
92            Graph::<MultiCompilerParsedSource>::resolve_sources(&self.project.paths, sources)?;
93
94        Ok(graph.imports(&self.target_path).into_iter().map(Into::into).collect())
95    }
96}
97
98/// An abstraction for various verification providers such as etherscan, sourcify, blockscout
99#[async_trait]
100pub trait VerificationProvider {
101    /// This should ensure the verify request can be prepared successfully.
102    ///
103    /// Caution: Implementers must ensure that this _never_ sends the actual verify request
104    /// `[VerificationProvider::verify]`, instead this is supposed to evaluate whether the given
105    /// [`VerifyArgs`] are valid to begin with. This should prevent situations where there's a
106    /// contract deployment that's executed before the verify request and the subsequent verify task
107    /// fails due to misconfiguration.
108    async fn preflight_verify_check(
109        &mut self,
110        args: VerifyArgs,
111        context: VerificationContext,
112    ) -> Result<()>;
113
114    /// Sends the actual verify request for the targeted contract.
115    async fn verify(&mut self, args: VerifyArgs, context: VerificationContext) -> Result<()>;
116
117    /// Checks whether the contract is verified.
118    async fn check(&self, args: VerifyCheckArgs) -> Result<()>;
119}
120
121impl FromStr for VerificationProviderType {
122    type Err = String;
123
124    fn from_str(s: &str) -> Result<Self, Self::Err> {
125        match s {
126            "e" | "etherscan" => Ok(Self::Etherscan),
127            "s" | "sourcify" => Ok(Self::Sourcify),
128            "b" | "blockscout" => Ok(Self::Blockscout),
129            "o" | "oklink" => Ok(Self::Oklink),
130            "c" | "custom" => Ok(Self::Custom),
131            _ => Err(format!("Unknown provider: {s}")),
132        }
133    }
134}
135
136impl fmt::Display for VerificationProviderType {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        match self {
139            Self::Etherscan => {
140                write!(f, "etherscan")?;
141            }
142            Self::Sourcify => {
143                write!(f, "sourcify")?;
144            }
145            Self::Blockscout => {
146                write!(f, "blockscout")?;
147            }
148            Self::Oklink => {
149                write!(f, "oklink")?;
150            }
151            Self::Custom => {
152                write!(f, "custom")?;
153            }
154        };
155        Ok(())
156    }
157}
158
159#[derive(Clone, Debug, Default, PartialEq, Eq, clap::ValueEnum)]
160pub enum VerificationProviderType {
161    Etherscan,
162    #[default]
163    Sourcify,
164    Blockscout,
165    Oklink,
166    /// Custom verification provider, requires compatibility with the Etherscan API.
167    Custom,
168}
169
170impl VerificationProviderType {
171    /// Returns the corresponding `VerificationProvider` for the key
172    pub fn client(&self, key: Option<&str>) -> Result<Box<dyn VerificationProvider>> {
173        let has_key = key.as_ref().is_some_and(|k| !k.is_empty());
174        // 1. If no verifier or `--verifier sourcify` is set and no API key provided, use Sourcify.
175        if !has_key && self.is_sourcify() {
176            sh_println!(
177            "Attempting to verify on Sourcify. Pass the --etherscan-api-key <API_KEY> to verify on Etherscan, \
178            or use the --verifier flag to verify on another provider."
179        )?;
180            return Ok(Box::<SourcifyVerificationProvider>::default());
181        }
182
183        // 2. If `--verifier etherscan` is explicitly set, enforce the API key requirement.
184        if self.is_etherscan() {
185            if !has_key {
186                eyre::bail!("ETHERSCAN_API_KEY must be set to use Etherscan as a verifier")
187            }
188            return Ok(Box::<EtherscanVerificationProvider>::default());
189        }
190
191        // 3. If `--verifier blockscout | oklink | custom` is explicitly set, use the chosen
192        //    verifier.
193        if matches!(self, Self::Blockscout | Self::Oklink | Self::Custom) {
194            return Ok(Box::<EtherscanVerificationProvider>::default());
195        }
196
197        // 4. If no `--verifier` is specified but `ETHERSCAN_API_KEY` is set, default to Etherscan.
198        if has_key {
199            return Ok(Box::<EtherscanVerificationProvider>::default());
200        }
201
202        // 5. If no valid provider is specified, bail.
203        eyre::bail!("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.")
204    }
205
206    pub fn is_sourcify(&self) -> bool {
207        matches!(self, Self::Sourcify)
208    }
209
210    pub fn is_etherscan(&self) -> bool {
211        matches!(self, Self::Etherscan)
212    }
213}