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 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 break;
88 }
89 }
90 }
91 }
92
93 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 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 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#[derive(Clone)]
125pub enum SendTransactionKind<'a> {
126 Unlocked(WithOtherFields<TransactionRequest>),
127 Raw(WithOtherFields<TransactionRequest>, &'a EthereumWallet),
128 Signed(TxEnvelope),
129}
130
131pub enum SendTransactionsKind {
133 Unlocked(AddressHashSet),
135 Raw(AddressHashMap<EthereumWallet>),
137}
138
139impl SendTransactionsKind {
140 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
166pub 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 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 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 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 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 let sequential_broadcast = estimate_via_rpc ||
352 self.args.slow ||
353 required_addresses.len() != 1 ||
354 !has_batch_support(sequence.chain);
355
356 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 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 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 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}