diff --git a/crates/script/src/progress.rs b/crates/script/src/progress.rs
index 314c4d4983f3a..58d2270a8231e 100644
--- a/crates/script/src/progress.rs
+++ b/crates/script/src/progress.rs
@@ -1,13 +1,15 @@
use crate::receipts::{PendingReceiptError, TxStatus, check_tx_status, format_receipt};
use alloy_chains::Chain;
use alloy_primitives::{
- B256,
+ Address, B256,
map::{B256HashMap, HashMap},
};
use eyre::Result;
use forge_script_sequence::ScriptSequence;
+use forge_verify::provider::VerificationProviderType;
use foundry_cli::utils::init_progress;
use foundry_common::{provider::RetryProvider, shell};
+use foundry_compilers::artifacts::EvmVersion;
use futures::StreamExt;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use parking_lot::RwLock;
@@ -285,3 +287,144 @@ Add `--resume` to your command to try and continue broadcasting the transactions
Ok(())
}
}
+
+/// State of [ProgressBar]s displayed for contract verification.
+/// Shows progress of all contracts being verified.
+/// For each contract an individual progress bar is displayed.
+/// When a contract verification completes, their progress is removed and result summary is
+/// displayed.
+#[derive(Debug)]
+pub struct VerificationProgressState {
+ /// Main [MultiProgress] instance showing progress for all verifications.
+ multi: MultiProgress,
+ /// Progress bar counting completed / remaining verifications.
+ overall_progress: ProgressBar,
+ /// Individual contract verification progress.
+ contracts_progress: HashMap
,
+ /// Contract details for display.
+ contract_details: HashMap,
+}
+
+impl VerificationProgressState {
+ /// Creates overall verification progress state.
+ pub fn new(contracts_len: usize) -> Self {
+ let multi = MultiProgress::new();
+ let overall_progress = multi.add(ProgressBar::new(contracts_len as u64));
+ overall_progress.set_style(
+ indicatif::ProgressStyle::with_template("{bar:40.cyan/blue} {pos:>7}/{len:7} {msg}")
+ .unwrap()
+ .progress_chars("##-"),
+ );
+ overall_progress.set_message("verifications completed");
+ Self {
+ multi,
+ overall_progress,
+ contracts_progress: HashMap::default(),
+ contract_details: HashMap::default(),
+ }
+ }
+
+ /// Creates new contract verification progress and add it to overall progress.
+ /// Displays contract details inline.
+ #[allow(clippy::too_many_arguments)]
+ pub fn start_contract_progress(
+ &mut self,
+ address: Address,
+ contract_name: Option<&str>,
+ chain: Chain,
+ evm_version: Option<&EvmVersion>,
+ compiler_version: Option<&str>,
+ constructor_args: Option<&str>,
+ verifier: &VerificationProviderType,
+ ) {
+ let contract_progress = self.multi.add(ProgressBar::new_spinner());
+ contract_progress.set_style(
+ indicatif::ProgressStyle::with_template("{spinner} {wide_msg:.bold.dim}")
+ .unwrap()
+ .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "),
+ );
+
+ let display_name = contract_name
+ .map(|n| format!("{n} ({address})"))
+ .unwrap_or_else(|| format!("{address}"));
+
+ // Build details string
+ let mut details = format!("{display_name}\n");
+ details.push_str(&format!(" - chain: {chain}\n"));
+ if let Some(evm) = evm_version {
+ details.push_str(&format!(" - EVM version: {evm}\n"));
+ }
+ if let Some(compiler) = compiler_version {
+ details.push_str(&format!(" - Compiler version: {compiler}\n"));
+ }
+ if let Some(args) = constructor_args
+ && !args.is_empty()
+ {
+ details.push_str(&format!(" - Constructor args: {args}\n"));
+ }
+ details.push_str(&format!(" - verifier: {verifier}"));
+
+ // Store details for later use
+ self.contract_details.insert(address, details.clone());
+
+ contract_progress.set_message(details);
+ contract_progress.enable_steady_tick(Duration::from_millis(100));
+ self.contracts_progress.insert(address, contract_progress);
+ }
+
+ /// Updates contract verification progress message inline (as last entry).
+ /// This can be used to show intermediate progress messages like "Submitted contract for
+ /// verification" or "Verification Job ID: ..." during the verification process.
+ #[allow(dead_code)]
+ pub fn update_contract_progress(&mut self, address: Address, message: &str) {
+ if let Some(contract_progress) = self.contracts_progress.get(&address) {
+ if let Some(details) = self.contract_details.get(&address) {
+ let updated_message = format!("{details}\n - {message}");
+ contract_progress.set_message(updated_message);
+ } else {
+ contract_progress.set_message(message.to_string());
+ }
+ }
+ }
+
+ /// Prints contract verification result summary and removes it from overall progress.
+ /// Updates progress message inline instead of using suspend to avoid progress bar flakiness.
+ pub fn end_contract_progress(&mut self, address: Address, success: bool, error: Option<&str>) {
+ if let Some(contract_progress) = self.contracts_progress.remove(&address) {
+ // Update message inline as last entry
+ let status = if success { "✓ Verified".green() } else { "✗ Failed".red() };
+ let final_message = if let Some(details) = self.contract_details.remove(&address) {
+ if let Some(err) = error {
+ format!("{details}\n - {status}: {err}")
+ } else {
+ format!("{details}\n - {status}")
+ }
+ } else if let Some(err) = error {
+ format!("{address}\n - {status}: {err}")
+ } else {
+ format!("{address}\n - {status}")
+ };
+ contract_progress.set_message(final_message);
+ contract_progress.finish_and_clear();
+ // Increment overall progress bar to reflect completed verification.
+ self.overall_progress.inc(1);
+ }
+ }
+
+ /// Removes overall verification progress.
+ pub fn clear(&mut self) {
+ self.multi.clear().unwrap();
+ }
+}
+
+/// Cloneable wrapper around [VerificationProgressState].
+#[derive(Debug, Clone)]
+pub struct VerificationProgress {
+ pub inner: Arc>,
+}
+
+impl VerificationProgress {
+ pub fn new(contracts_len: usize) -> Self {
+ Self { inner: Arc::new(RwLock::new(VerificationProgressState::new(contracts_len))) }
+ }
+}
diff --git a/crates/script/src/verify.rs b/crates/script/src/verify.rs
index ceec637e66661..f9b3235294564 100644
--- a/crates/script/src/verify.rs
+++ b/crates/script/src/verify.rs
@@ -1,6 +1,7 @@
use crate::{
ScriptArgs, ScriptConfig,
build::LinkedBuildData,
+ progress::VerificationProgress,
sequence::{ScriptSequenceKind, get_commit_hash},
};
use alloy_primitives::{Address, hex};
@@ -11,6 +12,7 @@ use foundry_cli::opts::{EtherscanOpts, ProjectPathOpts};
use foundry_common::ContractsByArtifact;
use foundry_compilers::{Project, artifacts::EvmVersion, info::ContractInfo};
use foundry_config::{Chain, Config};
+use futures::future::join_all;
use semver::Version;
/// State after we have broadcasted the script.
@@ -102,6 +104,7 @@ impl VerifyBundle {
/// Given a `VerifyBundle` and contract details, it tries to generate a valid `VerifyArgs` to
/// use against the `contract_address`.
+ /// Returns both the VerifyArgs and the contract name if found.
pub fn get_verify_args(
&self,
contract_address: Address,
@@ -109,7 +112,7 @@ impl VerifyBundle {
data: &[u8],
libraries: &[String],
evm_version: EvmVersion,
- ) -> Option {
+ ) -> Option<(VerifyArgs, String)> {
for (artifact, contract) in self.known_contracts.iter() {
let Some(bytecode) = contract.bytecode() else { continue };
// If it's a CREATE2, the tx.data comes with a 32-byte salt in the beginning
@@ -140,6 +143,7 @@ impl VerifyBundle {
artifact.version.patch,
);
+ let contract_name = contract.name.clone();
let verify = VerifyArgs {
address: contract_address,
contract: Some(contract),
@@ -168,7 +172,7 @@ impl VerifyBundle {
creation_transaction_hash: None,
};
- return Some(verify);
+ return Some((verify, contract_name));
}
}
None
@@ -190,12 +194,15 @@ async fn verify_contracts(
{
trace!(target: "script", "prepare future verifications");
- let mut future_verifications = Vec::with_capacity(sequence.receipts.len());
+ // Store verification info: (address, contract_name, contract_details, future)
+ let mut verification_tasks = Vec::new();
let mut unverifiable_contracts = vec![];
// Make sure the receipts have the right order first.
sequence.sort_receipts();
+ let chain: Chain = sequence.chain.into();
+
for (receipt, tx) in sequence.receipts.iter_mut().zip(sequence.transactions.iter()) {
// create2 hash offset
let mut offset = 0;
@@ -214,7 +221,20 @@ async fn verify_contracts(
&sequence.libraries,
config.evm_version,
) {
- Some(verify) => future_verifications.push(verify.run()),
+ Some((verify_args, contract_name)) => {
+ // Extract details before moving verify_args
+ let evm_version = verify_args.evm_version;
+ let compiler_version = verify_args.compiler_version.clone();
+ let constructor_args = verify_args.constructor_args.clone();
+ let verifier_type = verify_args.verifier.verifier.clone();
+ let future = verify_args.run();
+ verification_tasks.push((
+ address,
+ Some(contract_name),
+ (evm_version, compiler_version, constructor_args, verifier_type),
+ future,
+ ));
+ }
None => unverifiable_contracts.push(address),
};
}
@@ -228,30 +248,106 @@ async fn verify_contracts(
&sequence.libraries,
config.evm_version,
) {
- Some(verify) => future_verifications.push(verify.run()),
+ Some((verify_args, contract_name)) => {
+ // Extract details before moving verify_args
+ let evm_version = verify_args.evm_version;
+ let compiler_version = verify_args.compiler_version.clone();
+ let constructor_args = verify_args.constructor_args.clone();
+ let verifier_type = verify_args.verifier.verifier.clone();
+ let future = verify_args.run();
+ verification_tasks.push((
+ *address,
+ Some(contract_name),
+ (evm_version, compiler_version, constructor_args, verifier_type),
+ future,
+ ));
+ }
None => unverifiable_contracts.push(*address),
};
}
}
- trace!(target: "script", "collected {} verification jobs and {} unverifiable contracts", future_verifications.len(), unverifiable_contracts.len());
+ trace!(target: "script", "collected {} verification jobs and {} unverifiable contracts", verification_tasks.len(), unverifiable_contracts.len());
check_unverified(sequence, unverifiable_contracts, verify);
- let num_verifications = future_verifications.len();
- let mut num_of_successful_verifications = 0;
+ let num_verifications = verification_tasks.len();
+ if num_verifications == 0 {
+ return Ok(());
+ }
+
+ // Create progress tracker
+ let progress = VerificationProgress::new(num_verifications);
+ let progress_ref = &progress;
+
+ // Start progress for all contracts with details
+ for (
+ address,
+ contract_name,
+ (evm_version, compiler_version, constructor_args, verifier_type),
+ _,
+ ) in &verification_tasks
+ {
+ progress_ref.inner.write().start_contract_progress(
+ *address,
+ contract_name.as_deref(),
+ chain,
+ evm_version.as_ref(),
+ compiler_version.as_deref(),
+ constructor_args.as_deref(),
+ verifier_type,
+ );
+ }
+
+ // Wrap each verification future to track progress
+ let futures_with_progress: Vec<_> = verification_tasks
+ .into_iter()
+ .map(|(address, _contract_name, _contract_details, future)| {
+ let progress = progress_ref.clone();
+ async move {
+ let result = future.await;
+ let (success, error) = match &result {
+ Ok(_) => (true, None),
+ Err(err) => (false, Some(err.to_string())),
+ };
+ progress.inner.write().end_contract_progress(
+ address,
+ success,
+ error.as_deref(),
+ );
+ result
+ }
+ })
+ .collect();
+
sh_println!("##\nStart verification for ({num_verifications}) contracts")?;
- for verification in future_verifications {
- match verification.await {
+ let results = join_all(futures_with_progress).await;
+
+ // Collect all errors and successes
+ let mut num_of_successful_verifications = 0;
+ let mut errors = Vec::new();
+
+ for result in results {
+ match result {
Ok(_) => {
num_of_successful_verifications += 1;
}
Err(err) => {
- sh_err!("Failed to verify contract: {err:#}")?;
+ errors.push(err);
}
}
}
+ // Clear progress display
+ progress.inner.write().clear();
+
+ // Report results
+ if !errors.is_empty() {
+ for err in &errors {
+ sh_err!("Failed to verify contract: {err:#}")?;
+ }
+ }
+
if num_of_successful_verifications < num_verifications {
return Err(eyre!(
"Not all ({num_of_successful_verifications} / {num_verifications}) contracts were verified!"