1use std::{cmp::Ordering, sync::Arc, time::Duration};
2
3use alloy_chains::{Chain, NamedChain};
4use alloy_consensus::TxEnvelope;
5use alloy_eips::{BlockId, eip2718::Encodable2718};
6use alloy_network::{AnyNetwork, EthereumWallet, TransactionBuilder};
7use alloy_primitives::{
8 Address, TxHash,
9 map::{AddressHashMap, AddressHashSet},
10 utils::format_units,
11};
12use alloy_provider::{Provider, utils::Eip1559Estimation};
13use alloy_rpc_types::TransactionRequest;
14use alloy_serde::WithOtherFields;
15use eyre::{Context, Result, bail};
16use forge_verify::provider::VerificationProviderType;
17use foundry_cheatcodes::Wallets;
18use foundry_cli::utils::{has_batch_support, has_different_gas_calc};
19use foundry_common::{
20 TransactionMaybeSigned,
21 provider::{RetryProvider, get_http_provider, try_get_http_provider},
22 shell,
23};
24use foundry_config::Config;
25use foundry_wallets::{WalletSigner, wallet_browser::signer::BrowserSigner};
26use futures::{FutureExt, StreamExt, future::join_all, stream::FuturesUnordered};
27use itertools::Itertools;
28
29use crate::{
30 ScriptArgs, ScriptConfig, build::LinkedBuildData, progress::ScriptProgress,
31 sequence::ScriptSequenceKind, verify::BroadcastedState,
32};
33
34pub async fn estimate_gas<P: Provider<AnyNetwork>>(
35 tx: &mut WithOtherFields<TransactionRequest>,
36 provider: &P,
37 estimate_multiplier: u64,
38) -> Result<()> {
39 tx.gas = None;
42
43 tx.set_gas_limit(
44 provider.estimate_gas(tx.clone()).await.wrap_err("Failed to estimate gas for tx")?
45 * estimate_multiplier
46 / 100,
47 );
48 Ok(())
49}
50
51pub async fn next_nonce(
52 caller: Address,
53 provider_url: &str,
54 block_number: Option<u64>,
55) -> eyre::Result<u64> {
56 let provider = try_get_http_provider(provider_url)
57 .wrap_err_with(|| format!("bad fork_url provider: {provider_url}"))?;
58
59 let block_id = block_number.map_or(BlockId::latest(), BlockId::number);
60 Ok(provider.get_transaction_count(caller).block_id(block_id).await?)
61}
62
63#[derive(Clone)]
65pub enum SendTransactionKind<'a> {
66 Unlocked(WithOtherFields<TransactionRequest>),
67 Raw(WithOtherFields<TransactionRequest>, &'a EthereumWallet),
68 Browser(WithOtherFields<TransactionRequest>, &'a BrowserSigner),
69 Signed(TxEnvelope),
70}
71
72impl<'a> SendTransactionKind<'a> {
73 pub async fn prepare(
80 &mut self,
81 provider: &RetryProvider,
82 sequential_broadcast: bool,
83 is_fixed_gas_limit: bool,
84 estimate_via_rpc: bool,
85 estimate_multiplier: u64,
86 ) -> Result<()> {
87 if let Self::Raw(tx, _) | Self::Unlocked(tx) | Self::Browser(tx, _) = self {
88 if sequential_broadcast {
89 let from = tx.from.expect("no sender");
90
91 let tx_nonce = tx.nonce.expect("no nonce");
92 for attempt in 0..5 {
93 let nonce = provider.get_transaction_count(from).await?;
94 match nonce.cmp(&tx_nonce) {
95 Ordering::Greater => {
96 bail!(
97 "EOA nonce changed unexpectedly while sending transactions. Expected {tx_nonce} got {nonce} from provider."
98 )
99 }
100 Ordering::Less => {
101 if attempt == 4 {
102 bail!(
103 "After 5 attempts, provider nonce ({nonce}) is still behind expected nonce ({tx_nonce})."
104 )
105 }
106 warn!(
107 "Expected nonce ({tx_nonce}) is ahead of provider nonce ({nonce}). Retrying in 1 second..."
108 );
109 tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
110 }
111 Ordering::Equal => {
112 break;
114 }
115 }
116 }
117 }
118
119 if !is_fixed_gas_limit && estimate_via_rpc {
122 estimate_gas(tx, provider, estimate_multiplier).await?;
123 }
124 }
125
126 Ok(())
127 }
128
129 pub async fn send(self, provider: Arc<RetryProvider>) -> Result<TxHash> {
136 match self {
137 Self::Unlocked(tx) => {
138 debug!("sending transaction from unlocked account {:?}", tx);
139
140 let pending = provider.send_transaction(tx).await?;
142 Ok(*pending.tx_hash())
143 }
144 Self::Raw(tx, signer) => {
145 debug!("sending transaction: {:?}", tx);
146 let signed = tx.build(signer).await?;
147
148 let pending = provider.send_raw_transaction(signed.encoded_2718().as_ref()).await?;
150 Ok(*pending.tx_hash())
151 }
152 Self::Signed(tx) => {
153 debug!("sending transaction: {:?}", tx);
154 let pending = provider.send_raw_transaction(tx.encoded_2718().as_ref()).await?;
155 Ok(*pending.tx_hash())
156 }
157 Self::Browser(tx, signer) => {
158 debug!("sending transaction: {:?}", tx);
159
160 Ok(signer.send_transaction_via_browser(tx.into_inner()).await?)
162 }
163 }
164 }
165
166 pub async fn prepare_and_send(
171 mut self,
172 provider: Arc<RetryProvider>,
173 sequential_broadcast: bool,
174 is_fixed_gas_limit: bool,
175 estimate_via_rpc: bool,
176 estimate_multiplier: u64,
177 ) -> Result<TxHash> {
178 self.prepare(
179 &provider,
180 sequential_broadcast,
181 is_fixed_gas_limit,
182 estimate_via_rpc,
183 estimate_multiplier,
184 )
185 .await?;
186
187 self.send(provider).await
188 }
189}
190
191pub enum EitherSigner {
193 Ethereum(EthereumWallet),
194 Browser(BrowserSigner),
195}
196
197impl From<EthereumWallet> for EitherSigner {
198 fn from(wallet: EthereumWallet) -> Self {
199 Self::Ethereum(wallet)
200 }
201}
202
203impl From<WalletSigner> for EitherSigner {
204 fn from(wallet: WalletSigner) -> Self {
205 match wallet {
206 WalletSigner::Browser(wallet) => Self::Browser(wallet),
207 signer => EthereumWallet::new(signer).into(),
209 }
210 }
211}
212
213impl From<BrowserSigner> for EitherSigner {
214 fn from(wallet: BrowserSigner) -> Self {
215 Self::Browser(wallet)
216 }
217}
218
219pub enum SendTransactionsKind {
221 Unlocked(AddressHashSet),
223 Raw(AddressHashMap<EitherSigner>),
225}
226
227impl SendTransactionsKind {
228 pub fn for_sender(
232 &self,
233 addr: &Address,
234 tx: WithOtherFields<TransactionRequest>,
235 ) -> Result<SendTransactionKind<'_>> {
236 match self {
237 Self::Unlocked(unlocked) => {
238 if !unlocked.contains(addr) {
239 bail!("Sender address {:?} is not unlocked", addr)
240 }
241 Ok(SendTransactionKind::Unlocked(tx))
242 }
243 Self::Raw(wallets) => {
244 if let Some(wallet) = wallets.get(addr) {
245 match wallet {
246 EitherSigner::Ethereum(wallet) => Ok(SendTransactionKind::Raw(tx, wallet)),
247 EitherSigner::Browser(signer) => {
248 Ok(SendTransactionKind::Browser(tx, signer))
249 }
250 }
251 } else {
252 bail!("No matching signer for {:?} found", addr)
253 }
254 }
255 }
256 }
257}
258
259pub struct BundledState {
263 pub args: ScriptArgs,
264 pub script_config: ScriptConfig,
265 pub script_wallets: Wallets,
266 pub build_data: LinkedBuildData,
267 pub sequence: ScriptSequenceKind,
268}
269
270impl BundledState {
271 pub async fn wait_for_pending(mut self) -> Result<Self> {
272 let progress = ScriptProgress::default();
273 let progress_ref = &progress;
274 let futs = self
275 .sequence
276 .sequences_mut()
277 .iter_mut()
278 .enumerate()
279 .map(|(sequence_idx, sequence)| async move {
280 let rpc_url = sequence.rpc_url();
281 let provider = Arc::new(get_http_provider(rpc_url));
282 progress_ref
283 .wait_for_pending(
284 sequence_idx,
285 sequence,
286 &provider,
287 self.script_config.config.transaction_timeout,
288 )
289 .await
290 })
291 .collect::<Vec<_>>();
292
293 let errors = join_all(futs).await.into_iter().filter_map(Result::err).collect::<Vec<_>>();
294
295 self.sequence.save(true, false)?;
296
297 if !errors.is_empty() {
298 return Err(eyre::eyre!("{}", errors.iter().format("\n")));
299 }
300
301 Ok(self)
302 }
303
304 pub async fn broadcast(mut self) -> Result<BroadcastedState> {
306 let required_addresses = self
307 .sequence
308 .sequences()
309 .iter()
310 .flat_map(|sequence| {
311 sequence
312 .transactions()
313 .filter(|tx| tx.is_unsigned())
314 .map(|tx| tx.from().expect("missing from"))
315 })
316 .collect::<AddressHashSet>();
317
318 if required_addresses.contains(&Config::DEFAULT_SENDER) {
319 eyre::bail!(
320 "You seem to be using Foundry's default sender. Be sure to set your own --sender."
321 );
322 }
323
324 let send_kind = if self.args.unlocked {
325 SendTransactionsKind::Unlocked(required_addresses.clone())
326 } else {
327 let signers = self.script_wallets.into_multi_wallet().into_signers()?;
328 let mut missing_addresses = Vec::new();
329
330 for addr in &required_addresses {
331 if !signers.contains_key(addr) {
332 missing_addresses.push(addr);
333 }
334 }
335
336 if !missing_addresses.is_empty() {
337 eyre::bail!(
338 "No associated wallet for addresses: {:?}. Unlocked wallets: {:?}",
339 missing_addresses,
340 signers.keys().collect::<Vec<_>>()
341 );
342 }
343
344 let signers = signers.into_iter().map(|(addr, signer)| (addr, signer.into())).collect();
345
346 SendTransactionsKind::Raw(signers)
347 };
348
349 let progress = ScriptProgress::default();
350
351 for i in 0..self.sequence.sequences().len() {
352 let mut sequence = self.sequence.sequences_mut().get_mut(i).unwrap();
353
354 let provider = Arc::new(try_get_http_provider(sequence.rpc_url())?);
355 let already_broadcasted = sequence.receipts.len();
356
357 let seq_progress = progress.get_sequence_progress(i, sequence);
358
359 if already_broadcasted < sequence.transactions.len() {
360 let is_legacy = Chain::from(sequence.chain).is_legacy() || self.args.legacy;
361 let (gas_price, eip1559_fees) = match (
363 is_legacy,
364 self.args.with_gas_price,
365 self.args.priority_gas_price,
366 ) {
367 (true, Some(gas_price), _) => (Some(gas_price.to()), None),
368 (true, None, _) => (Some(provider.get_gas_price().await?), None),
369 (false, Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) => (
370 None,
371 Some(Eip1559Estimation {
372 max_fee_per_gas: max_fee_per_gas.to(),
373 max_priority_fee_per_gas: max_priority_fee_per_gas.to(),
374 }),
375 ),
376 (false, _, _) => {
377 let mut fees = provider.estimate_eip1559_fees().await.wrap_err("Failed to estimate EIP1559 fees. This chain might not support EIP1559, try adding --legacy to your command.")?;
378
379 if let Some(gas_price) = self.args.with_gas_price {
380 fees.max_fee_per_gas = gas_price.to();
381 }
382
383 if let Some(priority_gas_price) = self.args.priority_gas_price {
384 fees.max_priority_fee_per_gas = priority_gas_price.to();
385 }
386
387 (None, Some(fees))
388 }
389 };
390
391 let transactions = sequence
394 .transactions
395 .iter()
396 .skip(already_broadcasted)
397 .map(|tx_with_metadata| {
398 let is_fixed_gas_limit = tx_with_metadata.is_fixed_gas_limit;
399
400 let kind = match tx_with_metadata.tx().clone() {
401 TransactionMaybeSigned::Signed { tx, .. } => {
402 SendTransactionKind::Signed(tx)
403 }
404 TransactionMaybeSigned::Unsigned(mut tx) => {
405 let from = tx.from.expect("No sender for onchain transaction!");
406
407 tx.set_chain_id(sequence.chain);
408
409 if tx.to.is_none() {
412 tx.set_create();
413 }
414
415 if let Some(gas_price) = gas_price {
416 tx.set_gas_price(gas_price);
417 } else {
418 let eip1559_fees = eip1559_fees.expect("was set above");
419 tx.set_max_priority_fee_per_gas(
420 eip1559_fees.max_priority_fee_per_gas,
421 );
422 tx.set_max_fee_per_gas(eip1559_fees.max_fee_per_gas);
423 }
424
425 send_kind.for_sender(&from, tx)?
426 }
427 };
428
429 Ok((kind, is_fixed_gas_limit))
430 })
431 .collect::<Result<Vec<_>>>()?;
432
433 let estimate_via_rpc =
434 has_different_gas_calc(sequence.chain) || self.args.skip_simulation;
435
436 let sequential_broadcast = estimate_via_rpc
442 || self.args.slow
443 || required_addresses.len() != 1
444 || !has_batch_support(sequence.chain);
445
446 let batch_size = if sequential_broadcast { 1 } else { self.args.batch_size };
448 let mut index = already_broadcasted;
449
450 for (batch_number, batch) in transactions.chunks(batch_size).enumerate() {
451 seq_progress.inner.write().set_status(&format!(
452 "Sending transactions [{} - {}]",
453 batch_number * batch_size,
454 batch_number * batch_size + std::cmp::min(batch_size, batch.len()) - 1
455 ));
456
457 if !batch.is_empty() {
458 let pending_transactions =
459 batch.iter().map(|(kind, is_fixed_gas_limit)| {
460 let provider = provider.clone();
461 async move {
462 let res = kind
463 .clone()
464 .prepare_and_send(
465 provider,
466 sequential_broadcast,
467 *is_fixed_gas_limit,
468 estimate_via_rpc,
469 self.args.gas_estimate_multiplier,
470 )
471 .await;
472 (res, kind, 0, None)
473 }
474 .boxed()
475 });
476
477 let mut buffer = pending_transactions.collect::<FuturesUnordered<_>>();
478
479 'send: while let Some((res, kind, attempt, original_res)) =
480 buffer.next().await
481 {
482 if res.is_err() && attempt <= 3 {
483 let provider = provider.clone();
485 let progress = seq_progress.inner.clone();
486 buffer.push(Box::pin(async move {
487 debug!(err=?res, ?attempt, "retrying transaction ");
488 let attempt = attempt + 1;
489 progress.write().set_status(&format!(
490 "retrying transaction {res:?} (attempt {attempt})"
491 ));
492 tokio::time::sleep(Duration::from_millis(1000 * attempt)).await;
493 let r = kind.clone().send(provider).await;
494 (r, kind, attempt, original_res.or(Some(res)))
495 }));
496
497 continue 'send;
498 }
499
500 let tx_hash = res.wrap_err_with(|| {
502 if let Some(original_res) = original_res {
503 format!(
504 "Failed to send transaction after {attempt} attempts {original_res:?}"
505 )
506 } else {
507 "Failed to send transaction".to_string()
508 }
509 })?;
510 sequence.add_pending(index, tx_hash);
511
512 self.sequence.save(true, false)?;
514 sequence = self.sequence.sequences_mut().get_mut(i).unwrap();
515
516 seq_progress.inner.write().tx_sent(tx_hash);
517 index += 1;
518 }
519
520 self.sequence.save(true, false)?;
522 sequence = self.sequence.sequences_mut().get_mut(i).unwrap();
523
524 progress
525 .wait_for_pending(
526 i,
527 sequence,
528 &provider,
529 self.script_config.config.transaction_timeout,
530 )
531 .await?
532 }
533 self.sequence.save(true, false)?;
535 sequence = self.sequence.sequences_mut().get_mut(i).unwrap();
536 }
537 }
538
539 let (total_gas, total_gas_price, total_paid) =
540 sequence.receipts.iter().fold((0, 0, 0), |acc, receipt| {
541 let gas_used = receipt.gas_used;
542 let gas_price = receipt.effective_gas_price as u64;
543 (acc.0 + gas_used, acc.1 + gas_price, acc.2 + gas_used * gas_price)
544 });
545 let paid = format_units(total_paid, 18).unwrap_or_else(|_| "N/A".to_string());
546 let avg_gas_price = if sequence.receipts.is_empty() {
547 "N/A".to_string()
548 } else {
549 format_units(total_gas_price / sequence.receipts.len() as u64, 9)
550 .unwrap_or_else(|_| "N/A".to_string())
551 };
552
553 let token_symbol = NamedChain::try_from(sequence.chain)
554 .unwrap_or_default()
555 .native_currency_symbol()
556 .unwrap_or("ETH");
557 seq_progress.inner.write().set_status(&format!(
558 "Total Paid: {} {} ({} gas * avg {} gwei)\n",
559 paid.trim_end_matches('0'),
560 token_symbol,
561 total_gas,
562 avg_gas_price.trim_end_matches('0').trim_end_matches('.')
563 ));
564 seq_progress.inner.write().finish();
565 }
566
567 if !shell::is_json() {
568 sh_println!("\n\n==========================")?;
569 sh_println!("\nONCHAIN EXECUTION COMPLETE & SUCCESSFUL.")?;
570 }
571
572 Ok(BroadcastedState {
573 args: self.args,
574 script_config: self.script_config,
575 build_data: self.build_data,
576 sequence: self.sequence,
577 })
578 }
579
580 pub fn verify_preflight_check(&self) -> Result<()> {
581 for sequence in self.sequence.sequences() {
582 if self.args.verifier.verifier == VerificationProviderType::Etherscan
583 && self
584 .script_config
585 .config
586 .get_etherscan_api_key(Some(sequence.chain.into()))
587 .is_none()
588 {
589 eyre::bail!("Missing etherscan key for chain {}", sequence.chain);
590 }
591 }
592
593 Ok(())
594 }
595}