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#[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 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 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 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#[async_trait]
100pub trait VerificationProvider {
101 async fn preflight_verify_check(
109 &mut self,
110 args: VerifyArgs,
111 context: VerificationContext,
112 ) -> Result<()>;
113
114 async fn verify(&mut self, args: VerifyArgs, context: VerificationContext) -> Result<()>;
116
117 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,
168}
169
170impl VerificationProviderType {
171 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 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 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 if matches!(self, Self::Blockscout | Self::Oklink | Self::Custom) {
194 return Ok(Box::<EtherscanVerificationProvider>::default());
195 }
196
197 if has_key {
199 return Ok(Box::<EtherscanVerificationProvider>::default());
200 }
201
202 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}