1use crate::{
2 ScriptArgs, ScriptConfig,
3 build::LinkedBuildData,
4 sequence::{ScriptSequenceKind, get_commit_hash},
5};
6use alloy_primitives::{Address, hex};
7use eyre::{Result, eyre};
8use forge_script_sequence::{AdditionalContract, ScriptSequence};
9use forge_verify::{RetryArgs, VerifierArgs, VerifyArgs, provider::VerificationProviderType};
10use foundry_cli::opts::{EtherscanOpts, ProjectPathOpts};
11use foundry_common::ContractsByArtifact;
12use foundry_compilers::{Project, artifacts::EvmVersion, info::ContractInfo};
13use foundry_config::{Chain, Config};
14use semver::Version;
15
16pub struct BroadcastedState {
20 pub args: ScriptArgs,
21 pub script_config: ScriptConfig,
22 pub build_data: LinkedBuildData,
23 pub sequence: ScriptSequenceKind,
24}
25
26impl BroadcastedState {
27 pub async fn verify(self) -> Result<()> {
28 let Self { args, script_config, build_data, mut sequence, .. } = self;
29
30 let verify = VerifyBundle::new(
31 &script_config.config.project()?,
32 &script_config.config,
33 build_data.known_contracts,
34 args.retry,
35 args.verifier,
36 );
37
38 for sequence in sequence.sequences_mut() {
39 verify_contracts(sequence, &script_config.config, verify.clone()).await?;
40 }
41
42 Ok(())
43 }
44}
45
46#[derive(Clone)]
48pub struct VerifyBundle {
49 pub num_of_optimizations: Option<usize>,
50 pub known_contracts: ContractsByArtifact,
51 pub project_paths: ProjectPathOpts,
52 pub etherscan: EtherscanOpts,
53 pub retry: RetryArgs,
54 pub verifier: VerifierArgs,
55 pub via_ir: bool,
56}
57
58impl VerifyBundle {
59 pub fn new(
60 project: &Project,
61 config: &Config,
62 known_contracts: ContractsByArtifact,
63 retry: RetryArgs,
64 verifier: VerifierArgs,
65 ) -> Self {
66 let num_of_optimizations =
67 if config.optimizer == Some(true) { config.optimizer_runs } else { None };
68
69 let config_path = config.get_config_path();
70
71 let project_paths = ProjectPathOpts {
72 root: Some(project.paths.root.clone()),
73 contracts: Some(project.paths.sources.clone()),
74 remappings: project.paths.remappings.clone(),
75 remappings_env: None,
76 cache_path: Some(project.paths.cache.clone()),
77 lib_paths: project.paths.libraries.clone(),
78 hardhat: config.profile == Config::HARDHAT_PROFILE,
79 config_path: if config_path.exists() { Some(config_path) } else { None },
80 };
81
82 let via_ir = config.via_ir;
83
84 Self {
85 num_of_optimizations,
86 known_contracts,
87 etherscan: Default::default(),
88 project_paths,
89 retry,
90 verifier,
91 via_ir,
92 }
93 }
94
95 pub fn set_chain(&mut self, config: &Config, chain: Chain) {
97 self.etherscan.key = config.get_etherscan_api_key(Some(chain));
100 self.etherscan.chain = Some(chain);
101 }
102
103 pub fn get_verify_args(
106 &self,
107 contract_address: Address,
108 create2_offset: usize,
109 data: &[u8],
110 libraries: &[String],
111 evm_version: EvmVersion,
112 ) -> Option<VerifyArgs> {
113 for (artifact, contract) in self.known_contracts.iter() {
114 let Some(bytecode) = contract.bytecode() else { continue };
115 if data.split_at(create2_offset).1.starts_with(bytecode) {
118 let constructor_args = data.split_at(create2_offset + bytecode.len()).1.to_vec();
119
120 if artifact.source.extension().is_some_and(|e| e.to_str() == Some("vy")) {
121 warn!("Skipping verification of Vyper contract: {}", artifact.name);
122 return None;
123 }
124
125 let contract = ContractInfo {
127 path: Some(artifact.source.to_string_lossy().to_string()),
128 name: artifact
129 .name
130 .strip_suffix(&format!(".{}", &artifact.profile))
131 .unwrap_or_else(|| &artifact.name)
132 .to_string(),
133 };
134
135 let version = Version::new(
139 artifact.version.major,
140 artifact.version.minor,
141 artifact.version.patch,
142 );
143
144 let verify = VerifyArgs {
145 address: contract_address,
146 contract: Some(contract),
147 compiler_version: Some(version.to_string()),
148 constructor_args: Some(hex::encode(constructor_args)),
149 constructor_args_path: None,
150 no_auto_detect: false,
151 use_solc: None,
152 num_of_optimizations: self.num_of_optimizations,
153 etherscan: self.etherscan.clone(),
154 rpc: Default::default(),
155 flatten: false,
156 force: false,
157 skip_is_verified_check: true,
158 watch: true,
159 retry: self.retry,
160 libraries: libraries.to_vec(),
161 root: None,
162 verifier: self.verifier.clone(),
163 via_ir: self.via_ir,
164 evm_version: Some(evm_version),
165 show_standard_json_input: false,
166 guess_constructor_args: false,
167 compilation_profile: Some(artifact.profile.to_string()),
168 language: None,
169 creation_transaction_hash: None,
170 };
171
172 return Some(verify);
173 }
174 }
175 None
176 }
177}
178
179async fn verify_contracts(
182 sequence: &mut ScriptSequence,
183 config: &Config,
184 mut verify: VerifyBundle,
185) -> Result<()> {
186 trace!(target: "script", "verifying {} contracts [{}]", verify.known_contracts.len(), sequence.chain);
187
188 verify.set_chain(config, sequence.chain.into());
189
190 if verify.etherscan.has_key() || verify.verifier.verifier != VerificationProviderType::Etherscan
191 {
192 trace!(target: "script", "prepare future verifications");
193
194 let mut future_verifications = Vec::with_capacity(sequence.receipts.len());
195 let mut unverifiable_contracts = vec![];
196
197 sequence.sort_receipts();
199
200 for (receipt, tx) in sequence.receipts.iter_mut().zip(sequence.transactions.iter()) {
201 let mut offset = 0;
203
204 if tx.is_create2() {
205 receipt.contract_address = tx.contract_address;
206 offset = 32;
207 }
208
209 if let (Some(address), Some(data)) = (receipt.contract_address, tx.tx().input()) {
211 match verify.get_verify_args(
212 address,
213 offset,
214 data,
215 &sequence.libraries,
216 config.evm_version,
217 ) {
218 Some(verify) => future_verifications.push(verify.run()),
219 None => unverifiable_contracts.push(address),
220 };
221 }
222
223 for AdditionalContract { address, init_code, .. } in &tx.additional_contracts {
225 match verify.get_verify_args(
226 *address,
227 0,
228 init_code.as_ref(),
229 &sequence.libraries,
230 config.evm_version,
231 ) {
232 Some(verify) => future_verifications.push(verify.run()),
233 None => unverifiable_contracts.push(*address),
234 };
235 }
236 }
237
238 trace!(target: "script", "collected {} verification jobs and {} unverifiable contracts", future_verifications.len(), unverifiable_contracts.len());
239
240 check_unverified(sequence, unverifiable_contracts, verify);
241
242 let num_verifications = future_verifications.len();
243 let mut num_of_successful_verifications = 0;
244 sh_println!("##\nStart verification for ({num_verifications}) contracts")?;
245 for verification in future_verifications {
246 match verification.await {
247 Ok(_) => {
248 num_of_successful_verifications += 1;
249 }
250 Err(err) => {
251 sh_err!("Failed to verify contract: {err:#}")?;
252 }
253 }
254 }
255
256 if num_of_successful_verifications < num_verifications {
257 return Err(eyre!(
258 "Not all ({num_of_successful_verifications} / {num_verifications}) contracts were verified!"
259 ));
260 }
261
262 sh_println!("All ({num_verifications}) contracts were verified!")?;
263 }
264
265 Ok(())
266}
267
268fn check_unverified(
269 sequence: &ScriptSequence,
270 unverifiable_contracts: Vec<Address>,
271 verify: VerifyBundle,
272) {
273 if !unverifiable_contracts.is_empty() {
274 let _ = sh_warn!(
275 "We haven't found any matching bytecode for the following contracts: {:?}.\n\n\
276 This may occur when resuming a verification, but the underlying source code or compiler version has changed.\n\
277 Run `forge clean` to make sure builds are in sync with project files, then try again. Alternatively, use `forge verify-contract` to verify contracts that are already deployed.",
278 unverifiable_contracts
279 );
280
281 if let Some(commit) = &sequence.commit {
282 let current_commit = verify
283 .project_paths
284 .root
285 .map(|root| get_commit_hash(&root).unwrap_or_default())
286 .unwrap_or_default();
287
288 if ¤t_commit != commit {
289 let _ = sh_warn!(
290 "Script was broadcasted on commit `{commit}`, but we are at `{current_commit}`."
291 );
292 }
293 }
294 }
295}