forge_script/
receipts.rs
1use alloy_chains::Chain;
2use alloy_network::AnyTransactionReceipt;
3use alloy_primitives::{utils::format_units, TxHash, U256};
4use alloy_provider::{PendingTransactionBuilder, PendingTransactionError, Provider, WatchTxError};
5use eyre::{eyre, Result};
6use foundry_common::{provider::RetryProvider, retry, retry::RetryError, shell};
7use std::time::Duration;
8
9pub enum TxStatus {
11 Dropped,
12 Success(AnyTransactionReceipt),
13 Revert(AnyTransactionReceipt),
14}
15
16impl From<AnyTransactionReceipt> for TxStatus {
17 fn from(receipt: AnyTransactionReceipt) -> Self {
18 if !receipt.inner.inner.inner.receipt.status.coerce_status() {
19 Self::Revert(receipt)
20 } else {
21 Self::Success(receipt)
22 }
23 }
24}
25
26pub async fn check_tx_status(
29 provider: &RetryProvider,
30 hash: TxHash,
31 timeout: u64,
32) -> (TxHash, Result<TxStatus, eyre::Report>) {
33 let result = retry::Retry::new_no_delay(3)
34 .run_async_until_break(|| async {
35 match PendingTransactionBuilder::new(provider.clone(), hash)
36 .with_timeout(Some(Duration::from_secs(timeout)))
37 .get_receipt()
38 .await
39 {
40 Ok(receipt) => Ok(receipt.into()),
41 Err(e) => match provider.get_transaction_by_hash(hash).await {
42 Ok(_) => match e {
43 PendingTransactionError::TxWatcher(WatchTxError::Timeout) => {
44 Err(RetryError::Continue(eyre!(
45 "tx is still known to the node, waiting for receipt"
46 )))
47 }
48 _ => Err(RetryError::Retry(e.into())),
49 },
50 Err(_) => Ok(TxStatus::Dropped),
51 },
52 }
53 })
54 .await;
55
56 (hash, result)
57}
58
59pub fn format_receipt(chain: Chain, receipt: &AnyTransactionReceipt) -> String {
61 let gas_used = receipt.gas_used;
62 let gas_price = receipt.effective_gas_price;
63 let block_number = receipt.block_number.unwrap_or_default();
64 let success = receipt.inner.inner.inner.receipt.status.coerce_status();
65
66 if shell::is_json() {
67 let _ = sh_println!(
68 "{}",
69 serde_json::json!({
70 "chain": chain,
71 "status": if success {
72 "success"
73 } else {
74 "failed"
75 },
76 "tx_hash": receipt.transaction_hash,
77 "contract_address": receipt.contract_address.map(|addr| addr.to_string()),
78 "block_number": block_number,
79 "gas_used": gas_used,
80 "gas_price": gas_price,
81 })
82 );
83
84 String::new()
85 } else {
86 format!(
87 "\n##### {chain}\n{status} Hash: {tx_hash:?}{contract_address}\nBlock: {block_number}\n{gas}\n\n",
88 status = if success { "✅ [Success]" } else { "❌ [Failed]" },
89 tx_hash = receipt.transaction_hash,
90 contract_address = if let Some(addr) = &receipt.contract_address {
91 format!("\nContract Address: {}", addr.to_checksum(None))
92 } else {
93 String::new()
94 },
95 gas = if gas_price == 0 {
96 format!("Gas Used: {gas_used}")
97 } else {
98 let paid = format_units((gas_used as u128).saturating_mul(gas_price), 18)
99 .unwrap_or_else(|_| "N/A".into());
100 let gas_price =
101 format_units(U256::from(gas_price), 9).unwrap_or_else(|_| "N/A".into());
102 format!(
103 "Paid: {} ETH ({gas_used} gas * {} gwei)",
104 paid.trim_end_matches('0'),
105 gas_price.trim_end_matches('0').trim_end_matches('.')
106 )
107 },
108 )
109 }
110}