foundry_block_explorers/
verify.rs

1use crate::{Client, Response, Result};
2use alloy_primitives::Address;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// Arguments for verifying contracts
7#[derive(Debug, Clone, Serialize, Deserialize)]
8#[must_use]
9pub struct VerifyContract {
10    #[serde(rename = "contractaddress")]
11    pub address: Address,
12    #[serde(rename = "sourceCode")]
13    pub source: String,
14    #[serde(rename = "codeformat")]
15    pub code_format: CodeFormat,
16    /// if codeformat=solidity-standard-json-input, then expected as
17    /// `erc20.sol:erc20`
18    #[serde(rename = "contractname")]
19    pub contract_name: String,
20    #[serde(rename = "compilerversion")]
21    pub compiler_version: String,
22    /// applicable when codeformat=solidity-single-file
23    #[serde(rename = "optimizationUsed", skip_serializing_if = "Option::is_none")]
24    pub optimization_used: Option<String>,
25    /// applicable when codeformat=solidity-single-file
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub runs: Option<String>,
28    /// The constructor arguments for the contract, if any.
29    ///
30    /// NOTE: This is renamed as the misspelled `ethers-etherscan/src/verify.rs`. The reason for
31    /// this is that Etherscan has had this misspelling on their API for quite a long time, and
32    /// changing it would break verification with arguments.
33    ///
34    /// For instances (e.g. blockscout) that might support the proper spelling, the field
35    /// `blockscout_constructor_arguments` is populated with the exact arguments passed to this
36    /// field as well.
37    #[serde(rename = "constructorArguements", skip_serializing_if = "Option::is_none")]
38    pub constructor_arguments: Option<String>,
39    /// Properly spelled constructor arguments. This is needed as some blockscout instances
40    /// can identify the correct spelling instead of the misspelled version above.
41    #[serde(rename = "constructorArguments", skip_serializing_if = "Option::is_none")]
42    pub blockscout_constructor_arguments: Option<String>,
43    /// applicable when codeformat=solidity-single-file
44    #[serde(rename = "evmversion", skip_serializing_if = "Option::is_none")]
45    pub evm_version: Option<String>,
46    /// Use `--via-ir`.
47    #[serde(rename = "viaIR", skip_serializing_if = "Option::is_none")]
48    pub via_ir: Option<bool>,
49    #[serde(flatten)]
50    pub other: HashMap<String, String>,
51}
52
53impl VerifyContract {
54    pub fn new(
55        address: Address,
56        contract_name: String,
57        source: String,
58        compiler_version: String,
59    ) -> Self {
60        Self {
61            address,
62            source,
63            code_format: Default::default(),
64            contract_name,
65            compiler_version,
66            optimization_used: None,
67            runs: None,
68            constructor_arguments: None,
69            blockscout_constructor_arguments: None,
70            evm_version: None,
71            via_ir: None,
72            other: Default::default(),
73        }
74    }
75
76    pub fn runs(mut self, runs: u32) -> Self {
77        self.runs = Some(format!("{runs}"));
78        self
79    }
80
81    pub fn optimization(self, optimization: bool) -> Self {
82        if optimization {
83            self.optimized()
84        } else {
85            self.not_optimized()
86        }
87    }
88
89    pub fn optimized(mut self) -> Self {
90        self.optimization_used = Some("1".to_string());
91        self
92    }
93
94    pub fn not_optimized(mut self) -> Self {
95        self.optimization_used = Some("0".to_string());
96        self
97    }
98
99    pub fn code_format(mut self, code_format: CodeFormat) -> Self {
100        self.code_format = code_format;
101        self
102    }
103
104    pub fn evm_version(mut self, evm_version: impl Into<String>) -> Self {
105        self.evm_version = Some(evm_version.into());
106        self
107    }
108
109    pub fn via_ir(mut self, via_ir: bool) -> Self {
110        self.via_ir = Some(via_ir);
111        self
112    }
113
114    pub fn constructor_arguments(
115        mut self,
116        constructor_arguments: Option<impl Into<String>>,
117    ) -> Self {
118        let constructor_args = constructor_arguments.map(|s| {
119            s.into()
120                .trim()
121                // TODO is this correct?
122                .trim_start_matches("0x")
123                .to_string()
124        });
125        self.constructor_arguments.clone_from(&constructor_args);
126        self.blockscout_constructor_arguments = constructor_args;
127        self
128    }
129}
130
131/// Arguments for verifying a proxy contract
132#[derive(Debug, Clone, Serialize, Deserialize)]
133#[allow(missing_copy_implementations)]
134pub struct VerifyProxyContract {
135    /// Proxy contract's address
136    pub address: Address,
137    /// Implementation contract proxy points to - must be verified before call.
138    #[serde(default, rename = "expectedimplementation", skip_serializing_if = "Option::is_none")]
139    pub expected_impl: Option<Address>,
140}
141
142#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
143pub enum CodeFormat {
144    #[serde(rename = "solidity-single-file")]
145    SingleFile,
146
147    #[default]
148    #[serde(rename = "solidity-standard-json-input")]
149    StandardJsonInput,
150
151    #[serde(rename = "vyper-json")]
152    VyperJson,
153}
154
155impl AsRef<str> for CodeFormat {
156    fn as_ref(&self) -> &str {
157        match self {
158            CodeFormat::SingleFile => "solidity-single-file",
159            CodeFormat::StandardJsonInput => "solidity-standard-json-input",
160            CodeFormat::VyperJson => "vyper-json",
161        }
162    }
163}
164
165impl Client {
166    /// Submit Source Code for Verification
167    pub async fn submit_contract_verification(
168        &self,
169        contract: &VerifyContract,
170    ) -> Result<Response<String>> {
171        let body = self.create_query("contract", "verifysourcecode", contract);
172        self.post_form(&body).await
173    }
174
175    /// Check Source Code Verification Status with receipt received from
176    /// `[Self::submit_contract_verification]`
177    pub async fn check_contract_verification_status(
178        &self,
179        guid: impl AsRef<str>,
180    ) -> Result<Response<String>> {
181        let body = self.create_query(
182            "contract",
183            "checkverifystatus",
184            HashMap::from([("guid", guid.as_ref())]),
185        );
186        self.post_form(&body).await
187    }
188
189    /// Submit Proxy Contract for Verification
190    pub async fn submit_proxy_contract_verification(
191        &self,
192        contract: &VerifyProxyContract,
193    ) -> Result<Response<String>> {
194        let body = self.create_query("contract", "verifyproxycontract", contract);
195        self.post_form(&body).await
196    }
197
198    /// Check Proxy Contract Verification Status with receipt received from
199    /// `[Self::submit_proxy_contract_verification]`
200    pub async fn check_proxy_contract_verification_status(
201        &self,
202        guid: impl AsRef<str>,
203    ) -> Result<Response<String>> {
204        let body = self.create_query(
205            "contract",
206            "checkproxyverification",
207            HashMap::from([("guid", guid.as_ref())]),
208        );
209        self.post_form(&body).await
210    }
211}