ethers_etherscan/
verify.rs

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