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 = config.get_etherscan_api_key(Some(chain));
102 self.etherscan.chain = Some(chain);
103 }
104
105 pub fn get_verify_args(
108 &self,
109 contract_address: Address,
110 create2_offset: usize,
111 data: &[u8],
112 libraries: &[String],
113 evm_version: EvmVersion,
114 ) -> Option<VerifyArgs> {
115 for (artifact, contract) in self.known_contracts.iter() {
116 let Some(bytecode) = contract.bytecode() else { continue };
117 if data.split_at(create2_offset).1.starts_with(bytecode) {
120 let constructor_args = data.split_at(create2_offset + bytecode.len()).1.to_vec();
121
122 if artifact.source.extension().is_some_and(|e| e.to_str() == Some("vy")) {
123 warn!("Skipping verification of Vyper contract: {}", artifact.name);
124 return None;
125 }
126
127 let contract = ContractInfo {
129 path: Some(artifact.source.to_string_lossy().to_string()),
130 name: artifact
131 .name
132 .strip_suffix(&format!(".{}", &artifact.profile))
133 .unwrap_or_else(|| &artifact.name)
134 .to_string(),
135 };
136
137 let version = Version::new(
141 artifact.version.major,
142 artifact.version.minor,
143 artifact.version.patch,
144 );
145
146 let verify = VerifyArgs {
147 address: contract_address,
148 contract: Some(contract),
149 compiler_version: Some(version.to_string()),
150 constructor_args: Some(hex::encode(constructor_args)),
151 constructor_args_path: None,
152 no_auto_detect: false,
153 use_solc: None,
154 num_of_optimizations: self.num_of_optimizations,
155 etherscan: self.etherscan.clone(),
156 rpc: Default::default(),
157 flatten: false,
158 force: false,
159 skip_is_verified_check: true,
160 watch: true,
161 retry: self.retry,
162 libraries: libraries.to_vec(),
163 root: None,
164 verifier: self.verifier.clone(),
165 via_ir: self.via_ir,
166 evm_version: Some(evm_version),
167 show_standard_json_input: false,
168 guess_constructor_args: false,
169 compilation_profile: Some(artifact.profile.clone()),
170 language: None,
171 creation_transaction_hash: None,
172 };
173
174 return Some(verify);
175 }
176 }
177 None
178 }
179}
180
181async fn verify_contracts<FEN: FoundryEvmNetwork>(
184 sequence: &mut ScriptSequence<FEN::Network>,
185 config: &Config,
186 mut verify: VerifyBundle,
187) -> Result<()> {
188 trace!(target: "script", "verifying {} contracts [{}]", verify.known_contracts.len(), sequence.chain);
189
190 verify.set_chain(config, sequence.chain.into());
191
192 if verify.etherscan.has_key() || verify.verifier.verifier != VerificationProviderType::Etherscan
193 {
194 trace!(target: "script", "prepare future verifications");
195
196 let mut future_verifications = Vec::with_capacity(sequence.receipts.len());
197 let mut unverifiable_contracts = vec![];
198
199 sequence.sort_receipts();
201
202 for (receipt, tx) in sequence.receipts.iter_mut().zip(sequence.transactions.iter()) {
203 let mut offset = 0;
205
206 if tx.is_create2()
207 && let Some(contract_address) = tx.contract_address
208 {
209 receipt.set_contract_address(contract_address);
210 offset = 32;
211 }
212
213 if let (Some(address), Some(data)) = (receipt.contract_address(), tx.tx().input()) {
215 match verify.get_verify_args(
216 address,
217 offset,
218 data,
219 &sequence.libraries,
220 config.evm_version,
221 ) {
222 Some(verify) => future_verifications.push(verify.run()),
223 None => unverifiable_contracts.push(address),
224 };
225 }
226
227 for AdditionalContract { address, init_code, .. } in &tx.additional_contracts {
229 match verify.get_verify_args(
230 *address,
231 0,
232 init_code.as_ref(),
233 &sequence.libraries,
234 config.evm_version,
235 ) {
236 Some(verify) => future_verifications.push(verify.run()),
237 None => unverifiable_contracts.push(*address),
238 };
239 }
240 }
241
242 trace!(target: "script", "collected {} verification jobs and {} unverifiable contracts", future_verifications.len(), unverifiable_contracts.len());
243
244 check_unverified(sequence, unverifiable_contracts, verify);
245
246 let num_verifications = future_verifications.len();
247 let mut num_of_successful_verifications = 0;
248 sh_println!("##\nStart verification for ({num_verifications}) contracts")?;
249 for verification in future_verifications {
250 match verification.await {
251 Ok(_) => {
252 num_of_successful_verifications += 1;
253 }
254 Err(err) => {
255 sh_err!("Failed to verify contract: {err:#}")?;
256 }
257 }
258 }
259
260 if num_of_successful_verifications < num_verifications {
261 return Err(eyre!(
262 "Not all ({num_of_successful_verifications} / {num_verifications}) contracts were verified!"
263 ));
264 }
265
266 sh_println!("All ({num_verifications}) contracts were verified!")?;
267 }
268
269 Ok(())
270}
271
272fn check_unverified<N: Network>(
273 sequence: &ScriptSequence<N>,
274 unverifiable_contracts: Vec<Address>,
275 verify: VerifyBundle,
276) {
277 if !unverifiable_contracts.is_empty() {
278 let _ = sh_warn!(
279 "We haven't found any matching bytecode for the following contracts: {:?}.\n\n\
280 This may occur when resuming a verification, but the underlying source code or compiler version has changed.\n\
281 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.",
282 unverifiable_contracts
283 );
284
285 if let Some(commit) = &sequence.commit {
286 let current_commit = verify
287 .project_paths
288 .root
289 .map(|root| get_commit_hash(&root).unwrap_or_default())
290 .unwrap_or_default();
291
292 if ¤t_commit != commit {
293 let _ = sh_warn!(
294 "Script was broadcasted on commit `{commit}`, but we are at `{current_commit}`."
295 );
296 }
297 }
298 }
299}