use crate::{Client, Response, Result}; use ethers_core::types::Address; use serde::{Deserialize, Serialize}; use std::collections::HashMap; /// Arguments for verifying contracts #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VerifyContract { #[serde(rename = "contractaddress")] pub address: Address, #[serde(rename = "sourceCode")] pub source: String, #[serde(rename = "codeformat")] pub code_format: CodeFormat, /// if codeformat=solidity-standard-json-input, then expected as /// `erc20.sol:erc20` #[serde(rename = "contractname")] pub contract_name: String, #[serde(rename = "compilerversion")] pub compiler_version: String, /// applicable when codeformat=solidity-single-file #[serde(rename = "optimizationUsed", skip_serializing_if = "Option::is_none")] pub optimization_used: Option, /// applicable when codeformat=solidity-single-file #[serde(skip_serializing_if = "Option::is_none")] pub runs: Option, /// NOTE: there is a typo in the etherscan API `constructorArguements` #[serde(rename = "constructorArguements", skip_serializing_if = "Option::is_none")] pub constructor_arguments: Option, /// applicable when codeformat=solidity-single-file #[serde(rename = "evmversion", skip_serializing_if = "Option::is_none")] pub evm_version: Option, #[serde(flatten)] pub other: HashMap, } impl VerifyContract { pub fn new( address: Address, contract_name: String, source: String, compiler_version: String, ) -> Self { Self { address, source, code_format: Default::default(), contract_name, compiler_version, optimization_used: None, runs: None, constructor_arguments: None, evm_version: None, other: Default::default(), } } #[must_use] pub fn runs(mut self, runs: u32) -> Self { self.runs = Some(format!("{runs}")); self } #[must_use] pub fn optimization(self, optimization: bool) -> Self { if optimization { self.optimized() } else { self.not_optimized() } } #[must_use] pub fn optimized(mut self) -> Self { self.optimization_used = Some("1".to_string()); self } #[must_use] pub fn not_optimized(mut self) -> Self { self.optimization_used = Some("0".to_string()); self } #[must_use] pub fn code_format(mut self, code_format: CodeFormat) -> Self { self.code_format = code_format; self } #[must_use] pub fn evm_version(mut self, evm_version: impl Into) -> Self { self.evm_version = Some(evm_version.into()); self } #[must_use] pub fn constructor_arguments( mut self, constructor_arguments: Option>, ) -> Self { self.constructor_arguments = constructor_arguments.map(|s| { s.into() .trim() // TODO is this correct? .trim_start_matches("0x") .to_string() }); self } } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum CodeFormat { #[serde(rename = "solidity-single-file")] SingleFile, #[default] #[serde(rename = "solidity-standard-json-input")] StandardJsonInput, } impl AsRef for CodeFormat { fn as_ref(&self) -> &str { match self { CodeFormat::SingleFile => "solidity-single-file", CodeFormat::StandardJsonInput => "solidity-standard-json-input", } } } impl Client { /// Submit Source Code for Verification pub async fn submit_contract_verification( &self, contract: &VerifyContract, ) -> Result> { let body = self.create_query("contract", "verifysourcecode", contract); self.post_form(&body).await } /// Check Source Code Verification Status with receipt received from /// `[Self::submit_contract_verification]` pub async fn check_contract_verification_status( &self, guid: impl AsRef, ) -> Result> { let body = self.create_query( "contract", "checkverifystatus", HashMap::from([("guid", guid.as_ref())]), ); self.post_form(&body).await } }