forge_script/
broadcast.rs

1use crate::{
2    build::LinkedBuildData, progress::ScriptProgress, sequence::ScriptSequenceKind,
3    verify::BroadcastedState, ScriptArgs, ScriptConfig,
4};
5use alloy_chains::Chain;
6use alloy_consensus::TxEnvelope;
7use alloy_eips::{eip2718::Encodable2718, BlockId};
8use alloy_network::{AnyNetwork, EthereumWallet, TransactionBuilder};
9use alloy_primitives::{
10    map::{AddressHashMap, AddressHashSet},
11    utils::format_units,
12    Address, TxHash,
13};
14use alloy_provider::{utils::Eip1559Estimation, Provider};
15use alloy_rpc_types::TransactionRequest;
16use alloy_serde::WithOtherFields;
17use eyre::{bail, Context, Result};
18use forge_verify::provider::VerificationProviderType;
19use foundry_cheatcodes::Wallets;
20use foundry_cli::utils::{has_batch_support, has_different_gas_calc};
21use foundry_common::{
22    provider::{get_http_provider, try_get_http_provider, RetryProvider},
23    shell, TransactionMaybeSigned,
24};
25use foundry_config::Config;
26use futures::{future::join_all, StreamExt};
27use itertools::Itertools;
28use std::{cmp::Ordering, sync::Arc};
29
30pub async fn estimate_gas<P: Provider<AnyNetwork>>(
31    tx: &mut WithOtherFields<TransactionRequest>,
32    provider: &P,
33    estimate_multiplier: u64,
34) -> Result<()> {
35    // if already set, some RPC endpoints might simply return the gas value that is already
36    // set in the request and omit the estimate altogether, so we remove it here
37    tx.gas = None;
38
39    tx.set_gas_limit(
40        provider.estimate_gas(tx.clone()).await.wrap_err("Failed to estimate gas for tx")? *
41            estimate_multiplier /
42            100,
43    );
44    Ok(())
45}
46
47pub async fn next_nonce(
48    caller: Address,
49    provider_url: &str,
50    block_number: Option<u64>,
51) -> eyre::Result<u64> {
52    let provider = try_get_http_provider(provider_url)
53        .wrap_err_with(|| format!("bad fork_url provider: {provider_url}"))?;
54
55    let block_id = block_number.map_or(BlockId::latest(), BlockId::number);
56    Ok(provider.get_transaction_count(caller).block_id(block_id).await?)
57}
58
59pub async fn send_transaction(
60    provider: Arc<RetryProvider>,
61    mut kind: SendTransactionKind<'_>,
62    sequential_broadcast: bool,
63    is_fixed_gas_limit: bool,
64    estimate_via_rpc: bool,
65    estimate_multiplier: u64,
66) -> Result<TxHash> {
67    if let SendTransactionKind::Raw(tx, _) | SendTransactionKind::Unlocked(tx) = &mut kind {
68        if sequential_broadcast {
69            let from = tx.from.expect("no sender");
70
71            let tx_nonce = tx.nonce.expect("no nonce");
72            for attempt in 0..5 {
73                let nonce = provider.get_transaction_count(from).await?;
74                match nonce.cmp(&tx_nonce) {
75                    Ordering::Greater => {
76                        bail!("EOA nonce changed unexpectedly while sending transactions. Expected {tx_nonce} got {nonce} from provider.")
77                    }
78                    Ordering::Less => {
79                        if attempt == 4 {
80                            bail!("After 5 attempts, provider nonce ({nonce}) is still behind expected nonce ({tx_nonce}).")
81                        }
82                        warn!("Expected nonce ({tx_nonce}) is ahead of provider nonce ({nonce}). Retrying in 1 second...");
83                        tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
84                    }
85                    Ordering::Equal => {
86                        // Nonces are equal, we can proceed
87                        break;
88                    }
89                }
90            }
91        }
92
93        // Chains which use `eth_estimateGas` are being sent sequentially and require their
94        // gas to be re-estimated right before broadcasting.
95        if !is_fixed_gas_limit && estimate_via_rpc {
96            estimate_gas(tx, &provider, estimate_multiplier).await?;
97        }
98    }
99
100    let pending = match kind {
101        SendTransactionKind::Unlocked(tx) => {
102            debug!("sending transaction from unlocked account {:?}", tx);
103
104            // Submit the transaction
105            provider.send_transaction(tx).await?
106        }
107        SendTransactionKind::Raw(tx, signer) => {
108            debug!("sending transaction: {:?}", tx);
109            let signed = tx.build(signer).await?;
110
111            // Submit the raw transaction
112            provider.send_raw_transaction(signed.encoded_2718().as_ref()).await?
113        }
114        SendTransactionKind::Signed(tx) => {
115            debug!("sending transaction: {:?}", tx);
116            provider.send_raw_transaction(tx.encoded_2718().as_ref()).await?
117        }
118    };
119
120    Ok(*pending.tx_hash())
121}
122
123/// How to send a single transaction
124#[derive(Clone)]
125pub enum SendTransactionKind<'a> {
126    Unlocked(WithOtherFields<TransactionRequest>),
127    Raw(WithOtherFields<TransactionRequest>, &'a EthereumWallet),
128    Signed(TxEnvelope),
129}
130
131/// Represents how to send _all_ transactions
132pub enum SendTransactionsKind {
133    /// Send via `eth_sendTransaction` and rely on the  `from` address being unlocked.
134    Unlocked(AddressHashSet),
135    /// Send a signed transaction via `eth_sendRawTransaction`
136    Raw(AddressHashMap<EthereumWallet>),
137}
138
139impl SendTransactionsKind {
140    /// Returns the [`SendTransactionKind`] for the given address
141    ///
142    /// Returns an error if no matching signer is found or the address is not unlocked
143    pub fn for_sender(
144        &self,
145        addr: &Address,
146        tx: WithOtherFields<TransactionRequest>,
147    ) -> Result<SendTransactionKind<'_>> {
148        match self {
149            Self::Unlocked(unlocked) => {
150                if !unlocked.contains(addr) {
151                    bail!("Sender address {:?} is not unlocked", addr)
152                }
153                Ok(SendTransactionKind::Unlocked(tx))
154            }
155            Self::Raw(wallets) => {
156                if let Some(wallet) = wallets.get(addr) {
157                    Ok(SendTransactionKind::Raw(tx, wallet))
158                } else {
159                    bail!("No matching signer for {:?} found", addr)
160                }
161            }
162        }
163    }
164}
165
166/// State after we have bundled all
167/// [`TransactionWithMetadata`](forge_script_sequence::TransactionWithMetadata) objects into a
168/// single [`ScriptSequenceKind`] object containing one or more script sequences.
169pub struct BundledState {
170    pub args: ScriptArgs,
171    pub script_config: ScriptConfig,
172    pub script_wallets: Wallets,
173    pub build_data: LinkedBuildData,
174    pub sequence: ScriptSequenceKind,
175}
176
177impl BundledState {
178    pub async fn wait_for_pending(mut self) -> Result<Self> {
179        let progress = ScriptProgress::default();
180        let progress_ref = &progress;
181        let futs = self
182            .sequence
183            .sequences_mut()
184            .iter_mut()
185            .enumerate()
186            .map(|(sequence_idx, sequence)| async move {
187                let rpc_url = sequence.rpc_url();
188                let provider = Arc::new(get_http_provider(rpc_url));
189                progress_ref
190                    .wait_for_pending(
191                        sequence_idx,
192                        sequence,
193                        &provider,
194                        self.script_config.config.transaction_timeout,
195                    )
196                    .await
197            })
198            .collect::<Vec<_>>();
199
200        let errors = join_all(futs).await.into_iter().filter_map(Result::err).collect::<Vec<_>>();
201
202        self.sequence.save(true, false)?;
203
204        if !errors.is_empty() {
205            return Err(eyre::eyre!("{}", errors.iter().format("\n")));
206        }
207
208        Ok(self)
209    }
210
211    /// Broadcasts transactions from all sequences.
212    pub async fn broadcast(mut self) -> Result<BroadcastedState> {
213        let required_addresses = self
214            .sequence
215            .sequences()
216            .iter()
217            .flat_map(|sequence| {
218                sequence
219                    .transactions()
220                    .filter(|tx| tx.is_unsigned())
221                    .map(|tx| tx.from().expect("missing from"))
222            })
223            .collect::<AddressHashSet>();
224
225        if required_addresses.contains(&Config::DEFAULT_SENDER) {
226            eyre::bail!(
227                "You seem to be using Foundry's default sender. Be sure to set your own --sender."
228            );
229        }
230
231        let send_kind = if self.args.unlocked {
232            SendTransactionsKind::Unlocked(required_addresses.clone())
233        } else {
234            let signers = self.script_wallets.into_multi_wallet().into_signers()?;
235            let mut missing_addresses = Vec::new();
236
237            for addr in &required_addresses {
238                if !signers.contains_key(addr) {
239                    missing_addresses.push(addr);
240                }
241            }
242
243            if !missing_addresses.is_empty() {
244                eyre::bail!(
245                    "No associated wallet for addresses: {:?}. Unlocked wallets: {:?}",
246                    missing_addresses,
247                    signers.keys().collect::<Vec<_>>()
248                );
249            }
250
251            let signers = signers
252                .into_iter()
253                .map(|(addr, signer)| (addr, EthereumWallet::new(signer)))
254                .collect();
255
256            SendTransactionsKind::Raw(signers)
257        };
258
259        let progress = ScriptProgress::default();
260
261        for i in 0..self.sequence.sequences().len() {
262            let mut sequence = self.sequence.sequences_mut().get_mut(i).unwrap();
263
264            let provider = Arc::new(try_get_http_provider(sequence.rpc_url())?);
265            let already_broadcasted = sequence.receipts.len();
266
267            let seq_progress = progress.get_sequence_progress(i, sequence);
268
269            if already_broadcasted < sequence.transactions.len() {
270                let is_legacy = Chain::from(sequence.chain).is_legacy() || self.args.legacy;
271                // Make a one-time gas price estimation
272                let (gas_price, eip1559_fees) = match (
273                    is_legacy,
274                    self.args.with_gas_price,
275                    self.args.priority_gas_price,
276                ) {
277                    (true, Some(gas_price), _) => (Some(gas_price.to()), None),
278                    (true, None, _) => (Some(provider.get_gas_price().await?), None),
279                    (false, Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) => (
280                        None,
281                        Some(Eip1559Estimation {
282                            max_fee_per_gas: max_fee_per_gas.to(),
283                            max_priority_fee_per_gas: max_priority_fee_per_gas.to(),
284                        }),
285                    ),
286                    (false, _, _) => {
287                        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.")?;
288
289                        if let Some(gas_price) = self.args.with_gas_price {
290                            fees.max_fee_per_gas = gas_price.to();
291                        }
292
293                        if let Some(priority_gas_price) = self.args.priority_gas_price {
294                            fees.max_priority_fee_per_gas = priority_gas_price.to();
295                        }
296
297                        (None, Some(fees))
298                    }
299                };
300
301                // Iterate through transactions, matching the `from` field with the associated
302                // wallet. Then send the transaction. Panics if we find a unknown `from`
303                let transactions = sequence
304                    .transactions
305                    .iter()
306                    .skip(already_broadcasted)
307                    .map(|tx_with_metadata| {
308                        let is_fixed_gas_limit = tx_with_metadata.is_fixed_gas_limit;
309
310                        let kind = match tx_with_metadata.tx().clone() {
311                            TransactionMaybeSigned::Signed { tx, .. } => {
312                                SendTransactionKind::Signed(tx)
313                            }
314                            TransactionMaybeSigned::Unsigned(mut tx) => {
315                                let from = tx.from.expect("No sender for onchain transaction!");
316
317                                tx.set_chain_id(sequence.chain);
318
319                                // Set TxKind::Create explicitly to satisfy `check_reqd_fields` in
320                                // alloy
321                                if tx.to.is_none() {
322                                    tx.set_create();
323                                }
324
325                                if let Some(gas_price) = gas_price {
326                                    tx.set_gas_price(gas_price);
327                                } else {
328                                    let eip1559_fees = eip1559_fees.expect("was set above");
329                                    tx.set_max_priority_fee_per_gas(
330                                        eip1559_fees.max_priority_fee_per_gas,
331                                    );
332                                    tx.set_max_fee_per_gas(eip1559_fees.max_fee_per_gas);
333                                }
334
335                                send_kind.for_sender(&from, tx)?
336                            }
337                        };
338
339                        Ok((kind, is_fixed_gas_limit))
340                    })
341                    .collect::<Result<Vec<_>>>()?;
342
343                let estimate_via_rpc =
344                    has_different_gas_calc(sequence.chain) || self.args.skip_simulation;
345
346                // We only wait for a transaction receipt before sending the next transaction, if
347                // there is more than one signer. There would be no way of assuring
348                // their order otherwise.
349                // Or if the chain does not support batched transactions (eg. Arbitrum).
350                // Or if we need to invoke eth_estimateGas before sending transactions.
351                let sequential_broadcast = estimate_via_rpc ||
352                    self.args.slow ||
353                    required_addresses.len() != 1 ||
354                    !has_batch_support(sequence.chain);
355
356                // We send transactions and wait for receipts in batches.
357                let batch_size = if sequential_broadcast { 1 } else { self.args.batch_size };
358                let mut index = already_broadcasted;
359
360                for (batch_number, batch) in transactions.chunks(batch_size).enumerate() {
361                    let mut pending_transactions = vec![];
362
363                    seq_progress.inner.write().set_status(&format!(
364                        "Sending transactions [{} - {}]",
365                        batch_number * batch_size,
366                        batch_number * batch_size + std::cmp::min(batch_size, batch.len()) - 1
367                    ));
368                    for (kind, is_fixed_gas_limit) in batch {
369                        let fut = send_transaction(
370                            provider.clone(),
371                            kind.clone(),
372                            sequential_broadcast,
373                            *is_fixed_gas_limit,
374                            estimate_via_rpc,
375                            self.args.gas_estimate_multiplier,
376                        );
377                        pending_transactions.push(fut);
378                    }
379
380                    if !pending_transactions.is_empty() {
381                        let mut buffer = futures::stream::iter(pending_transactions).buffered(7);
382
383                        while let Some(tx_hash) = buffer.next().await {
384                            let tx_hash = tx_hash.wrap_err("Failed to send transaction")?;
385                            sequence.add_pending(index, tx_hash);
386
387                            // Checkpoint save
388                            self.sequence.save(true, false)?;
389                            sequence = self.sequence.sequences_mut().get_mut(i).unwrap();
390
391                            seq_progress.inner.write().tx_sent(tx_hash);
392                            index += 1;
393                        }
394
395                        // Checkpoint save
396                        self.sequence.save(true, false)?;
397                        sequence = self.sequence.sequences_mut().get_mut(i).unwrap();
398
399                        progress
400                            .wait_for_pending(
401                                i,
402                                sequence,
403                                &provider,
404                                self.script_config.config.transaction_timeout,
405                            )
406                            .await?
407                    }
408                    // Checkpoint save
409                    self.sequence.save(true, false)?;
410                    sequence = self.sequence.sequences_mut().get_mut(i).unwrap();
411                }
412            }
413
414            let (total_gas, total_gas_price, total_paid) =
415                sequence.receipts.iter().fold((0, 0, 0), |acc, receipt| {
416                    let gas_used = receipt.gas_used;
417                    let gas_price = receipt.effective_gas_price as u64;
418                    (acc.0 + gas_used, acc.1 + gas_price, acc.2 + gas_used * gas_price)
419                });
420            let paid = format_units(total_paid, 18).unwrap_or_else(|_| "N/A".to_string());
421            let avg_gas_price = format_units(total_gas_price / sequence.receipts.len() as u64, 9)
422                .unwrap_or_else(|_| "N/A".to_string());
423
424            seq_progress.inner.write().set_status(&format!(
425                "Total Paid: {} ETH ({} gas * avg {} gwei)\n",
426                paid.trim_end_matches('0'),
427                total_gas,
428                avg_gas_price.trim_end_matches('0').trim_end_matches('.')
429            ));
430            seq_progress.inner.write().finish();
431        }
432
433        if !shell::is_json() {
434            sh_println!("\n\n==========================")?;
435            sh_println!("\nONCHAIN EXECUTION COMPLETE & SUCCESSFUL.")?;
436        }
437
438        Ok(BroadcastedState {
439            args: self.args,
440            script_config: self.script_config,
441            build_data: self.build_data,
442            sequence: self.sequence,
443        })
444    }
445
446    pub fn verify_preflight_check(&self) -> Result<()> {
447        for sequence in self.sequence.sequences() {
448            if self.args.verifier.verifier == VerificationProviderType::Etherscan &&
449                self.script_config
450                    .config
451                    .get_etherscan_api_key(Some(sequence.chain.into()))
452                    .is_none()
453            {
454                eyre::bail!("Missing etherscan key for chain {}", sequence.chain);
455            }
456        }
457
458        Ok(())
459    }
460}