use crate::{
etherscan::EtherscanVerificationProvider,
sourcify::SourcifyVerificationProvider,
verify::{VerifyArgs, VerifyCheckArgs},
};
use alloy_json_abi::JsonAbi;
use async_trait::async_trait;
use eyre::{OptionExt, Result};
use foundry_common::compile::ProjectCompiler;
use foundry_compilers::{
artifacts::{output_selection::OutputSelection, Metadata, Source},
compilers::{multi::MultiCompilerParsedSource, solc::SolcCompiler},
multi::MultiCompilerSettings,
solc::Solc,
Graph, Project,
};
use foundry_config::Config;
use semver::Version;
use std::{fmt, path::PathBuf, str::FromStr};
#[derive(Debug, Clone)]
pub struct VerificationContext {
pub config: Config,
pub project: Project,
pub target_path: PathBuf,
pub target_name: String,
pub compiler_version: Version,
pub compiler_settings: MultiCompilerSettings,
}
impl VerificationContext {
pub fn new(
target_path: PathBuf,
target_name: String,
compiler_version: Version,
config: Config,
compiler_settings: MultiCompilerSettings,
) -> Result<Self> {
let mut project = config.project()?;
project.no_artifacts = true;
let solc = Solc::find_or_install(&compiler_version)?;
project.compiler.solc = Some(SolcCompiler::Specific(solc));
Ok(Self { config, project, target_name, target_path, compiler_version, compiler_settings })
}
pub fn get_target_abi(&self) -> Result<JsonAbi> {
let mut project = self.project.clone();
project.update_output_selection(|selection| {
*selection = OutputSelection::common_output_selection(["abi".to_string()])
});
let output = ProjectCompiler::new()
.quiet(true)
.files([self.target_path.clone()])
.compile(&project)?;
let artifact = output
.find(&self.target_path, &self.target_name)
.ok_or_eyre("failed to find target artifact when compiling for abi")?;
artifact.abi.clone().ok_or_eyre("target artifact does not have an ABI")
}
pub fn get_target_metadata(&self) -> Result<Metadata> {
let mut project = self.project.clone();
project.update_output_selection(|selection| {
*selection = OutputSelection::common_output_selection(["metadata".to_string()]);
});
let output = ProjectCompiler::new()
.quiet(true)
.files([self.target_path.clone()])
.compile(&project)?;
let artifact = output
.find(&self.target_path, &self.target_name)
.ok_or_eyre("failed to find target artifact when compiling for metadata")?;
artifact.metadata.clone().ok_or_eyre("target artifact does not have an ABI")
}
pub fn get_target_imports(&self) -> Result<Vec<PathBuf>> {
let mut sources = self.project.paths.read_input_files()?;
sources.insert(self.target_path.clone(), Source::read(&self.target_path)?);
let graph =
Graph::<MultiCompilerParsedSource>::resolve_sources(&self.project.paths, sources)?;
Ok(graph.imports(&self.target_path).into_iter().cloned().collect())
}
}
#[async_trait]
pub trait VerificationProvider {
async fn preflight_verify_check(
&mut self,
args: VerifyArgs,
context: VerificationContext,
) -> Result<()>;
async fn verify(&mut self, args: VerifyArgs, context: VerificationContext) -> Result<()>;
async fn check(&self, args: VerifyCheckArgs) -> Result<()>;
}
impl FromStr for VerificationProviderType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"e" | "etherscan" => Ok(Self::Etherscan),
"s" | "sourcify" => Ok(Self::Sourcify),
"b" | "blockscout" => Ok(Self::Blockscout),
"o" | "oklink" => Ok(Self::Oklink),
"c" | "custom" => Ok(Self::Custom),
_ => Err(format!("Unknown provider: {s}")),
}
}
}
impl fmt::Display for VerificationProviderType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Etherscan => {
write!(f, "etherscan")?;
}
Self::Sourcify => {
write!(f, "sourcify")?;
}
Self::Blockscout => {
write!(f, "blockscout")?;
}
Self::Oklink => {
write!(f, "oklink")?;
}
Self::Custom => {
write!(f, "custom")?;
}
};
Ok(())
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, clap::ValueEnum)]
pub enum VerificationProviderType {
#[default]
Etherscan,
Sourcify,
Blockscout,
Oklink,
Custom,
}
impl VerificationProviderType {
pub fn client(&self, key: &Option<String>) -> Result<Box<dyn VerificationProvider>> {
match self {
Self::Etherscan => {
if key.as_ref().is_none_or(|key| key.is_empty()) {
eyre::bail!("ETHERSCAN_API_KEY must be set")
}
Ok(Box::<EtherscanVerificationProvider>::default())
}
Self::Sourcify => Ok(Box::<SourcifyVerificationProvider>::default()),
Self::Blockscout => Ok(Box::<EtherscanVerificationProvider>::default()),
Self::Oklink => Ok(Box::<EtherscanVerificationProvider>::default()),
Self::Custom => Ok(Box::<EtherscanVerificationProvider>::default()),
}
}
}