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