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#[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 = 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#[async_trait]
99pub trait VerificationProvider {
100 async fn preflight_verify_check(
108 &mut self,
109 args: VerifyArgs,
110 context: VerificationContext,
111 ) -> Result<()>;
112
113 async fn verify(&mut self, args: VerifyArgs, context: VerificationContext) -> Result<()>;
115
116 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,
167}
168
169impl VerificationProviderType {
170 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 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 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 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 if has_key {
212 return Ok(Box::<EtherscanVerificationProvider>::default());
213 }
214
215 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}