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 retry: self.retry,
163 libraries: libraries.to_vec(),
164 root: None,
165 verifier: self.verifier.clone(),
166 via_ir: self.via_ir,
167 license_type: None,
168 evm_version: Some(evm_version),
169 show_standard_json_input: false,
170 guess_constructor_args: false,
171 compilation_profile: Some(artifact.profile.clone()),
172 language: None,
173 creation_transaction_hash: None,
174 };
175
176 return Some(verify);
177 }
178 }
179 None
180 }
181}
182
183async fn verify_contracts<FEN: FoundryEvmNetwork>(
186 sequence: &mut ScriptSequence<FEN::Network>,
187 config: &Config,
188 mut verify: VerifyBundle,
189) -> Result<()> {
190 trace!(target: "script", "verifying {} contracts [{}]", verify.known_contracts.len(), sequence.chain);
191
192 verify.set_chain(config, sequence.chain.into());
193
194 if verify.etherscan.has_key()
195 || verify.verifier.effective_type() != VerificationProviderType::Etherscan
196 {
197 trace!(target: "script", "prepare future verifications");
198
199 let mut future_verifications = Vec::with_capacity(sequence.receipts.len());
200 let mut unverifiable_contracts = vec![];
201
202 sequence.sort_receipts();
204
205 for (receipt, tx) in sequence.receipts.iter_mut().zip(sequence.transactions.iter()) {
206 let offset = if tx.is_create2()
208 && let Some(contract_address) = tx.contract_address
209 {
210 receipt.set_contract_address(contract_address);
211 32
212 } else {
213 0
214 };
215
216 if let (Some(address), Some(data)) = (receipt.contract_address(), tx.tx().input()) {
218 match verify.get_verify_args(
219 address,
220 offset,
221 data,
222 &sequence.libraries,
223 config.evm_version,
224 ) {
225 Some(verify) => future_verifications.push(verify.run()),
226 None => unverifiable_contracts.push(address),
227 };
228 }
229
230 for AdditionalContract { address, init_code, .. } in &tx.additional_contracts {
232 match verify.get_verify_args(
233 *address,
234 0,
235 init_code.as_ref(),
236 &sequence.libraries,
237 config.evm_version,
238 ) {
239 Some(verify) => future_verifications.push(verify.run()),
240 None => unverifiable_contracts.push(*address),
241 };
242 }
243 }
244
245 trace!(target: "script", "collected {} verification jobs and {} unverifiable contracts", future_verifications.len(), unverifiable_contracts.len());
246
247 check_unverified(sequence, unverifiable_contracts, verify);
248
249 let num_verifications = future_verifications.len();
250 let mut num_of_successful_verifications = 0;
251 sh_println!("##\nStart verification for ({num_verifications}) contracts")?;
252 for verification in future_verifications {
253 match verification.await {
254 Ok(_) => {
255 num_of_successful_verifications += 1;
256 }
257 Err(err) => {
258 sh_err!("Failed to verify contract: {err:#}")?;
259 }
260 }
261 }
262
263 if num_of_successful_verifications < num_verifications {
264 return Err(eyre!(
265 "Not all ({num_of_successful_verifications} / {num_verifications}) contracts were verified!"
266 ));
267 }
268
269 sh_println!("All ({num_verifications}) contracts were verified!")?;
270 }
271
272 Ok(())
273}
274
275fn check_unverified<N: Network>(
276 sequence: &ScriptSequence<N>,
277 unverifiable_contracts: Vec<Address>,
278 verify: VerifyBundle,
279) {
280 if !unverifiable_contracts.is_empty() {
281 let _ = sh_warn!(
282 "We haven't found any matching bytecode for the following contracts: {:?}.\n\n\
283 This may occur when resuming a verification, but the underlying source code or compiler version has changed.\n\
284 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.",
285 unverifiable_contracts
286 );
287
288 if let Some(commit) = &sequence.commit {
289 let current_commit = verify
290 .project_paths
291 .root
292 .map(|root| get_commit_hash(&root).unwrap_or_default())
293 .unwrap_or_default();
294
295 if ¤t_commit != commit {
296 let _ = sh_warn!(
297 "Script was broadcasted on commit `{commit}`, but we are at `{current_commit}`."
298 );
299 }
300 }
301 }
302}