Skip to main content

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 { self.optimized() } else { self.not_optimized() }
83    }
84
85    pub fn optimized(mut self) -> Self {
86        self.optimization_used = Some("1".to_string());
87        self
88    }
89
90    pub fn not_optimized(mut self) -> Self {
91        self.optimization_used = Some("0".to_string());
92        self
93    }
94
95    pub fn code_format(mut self, code_format: CodeFormat) -> Self {
96        self.code_format = code_format;
97        self
98    }
99
100    pub fn evm_version(mut self, evm_version: impl Into<String>) -> Self {
101        self.evm_version = Some(evm_version.into());
102        self
103    }
104
105    pub fn via_ir(mut self, via_ir: bool) -> Self {
106        self.via_ir = Some(via_ir);
107        self
108    }
109
110    pub fn constructor_arguments(
111        mut self,
112        constructor_arguments: Option<impl Into<String>>,
113    ) -> Self {
114        let constructor_args = constructor_arguments.map(|s| {
115            s.into()
116                .trim()
117                // TODO is this correct?
118                .trim_start_matches("0x")
119                .to_string()
120        });
121        self.constructor_arguments.clone_from(&constructor_args);
122        self.blockscout_constructor_arguments = constructor_args;
123        self
124    }
125}
126
127/// Arguments for verifying a proxy contract
128#[derive(Debug, Clone, Serialize, Deserialize)]
129#[allow(missing_copy_implementations)]
130pub struct VerifyProxyContract {
131    /// Proxy contract's address
132    pub address: Address,
133    /// Implementation contract proxy points to - must be verified before call.
134    #[serde(default, rename = "expectedimplementation", skip_serializing_if = "Option::is_none")]
135    pub expected_impl: Option<Address>,
136}
137
138#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
139pub enum CodeFormat {
140    #[serde(rename = "solidity-single-file")]
141    SingleFile,
142
143    #[default]
144    #[serde(rename = "solidity-standard-json-input")]
145    StandardJsonInput,
146
147    #[serde(rename = "vyper-json")]
148    VyperJson,
149}
150
151impl AsRef<str> for CodeFormat {
152    fn as_ref(&self) -> &str {
153        match self {
154            CodeFormat::SingleFile => "solidity-single-file",
155            CodeFormat::StandardJsonInput => "solidity-standard-json-input",
156            CodeFormat::VyperJson => "vyper-json",
157        }
158    }
159}
160
161impl Client {
162    /// Submit Source Code for Verification
163    pub async fn submit_contract_verification(
164        &self,
165        contract: &VerifyContract,
166    ) -> Result<Response<String>> {
167        let body = self.create_query("contract", "verifysourcecode", contract);
168        self.post_form(&body).await
169    }
170
171    /// Check Source Code Verification Status with receipt received from
172    /// `[Self::submit_contract_verification]`
173    pub async fn check_contract_verification_status(
174        &self,
175        guid: impl AsRef<str>,
176    ) -> Result<Response<String>> {
177        let body = self.create_query(
178            "contract",
179            "checkverifystatus",
180            HashMap::from([("guid", guid.as_ref())]),
181        );
182        self.post_form(&body).await
183    }
184
185    /// Submit Proxy Contract for Verification
186    pub async fn submit_proxy_contract_verification(
187        &self,
188        contract: &VerifyProxyContract,
189    ) -> Result<Response<String>> {
190        let body = self.create_query("contract", "verifyproxycontract", contract);
191        self.post_form(&body).await
192    }
193
194    /// Check Proxy Contract Verification Status with receipt received from
195    /// `[Self::submit_proxy_contract_verification]`
196    pub async fn check_proxy_contract_verification_status(
197        &self,
198        guid: impl AsRef<str>,
199    ) -> Result<Response<String>> {
200        let body = self.create_query(
201            "contract",
202            "checkproxyverification",
203            HashMap::from([("guid", guid.as_ref())]),
204        );
205        self.post_form(&body).await
206    }
207}