foundry_primitives/network/
receipt.rs

1use alloy_consensus::{Receipt, TxReceipt};
2use alloy_network::{AnyReceiptEnvelope, AnyTransactionReceipt, ReceiptResponse};
3use alloy_primitives::{Address, B256, BlockHash, TxHash, U64};
4use alloy_rpc_types::{ConversionError, Log, TransactionReceipt};
5use alloy_serde::WithOtherFields;
6use derive_more::AsRef;
7use op_alloy_consensus::{OpDepositReceipt, OpDepositReceiptWithBloom};
8use serde::{Deserialize, Serialize};
9
10use crate::FoundryReceiptEnvelope;
11
12#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, AsRef)]
13pub struct FoundryTxReceipt(pub WithOtherFields<TransactionReceipt<FoundryReceiptEnvelope<Log>>>);
14
15impl FoundryTxReceipt {
16    pub fn new(inner: TransactionReceipt<FoundryReceiptEnvelope<Log>>) -> Self {
17        Self(WithOtherFields::new(inner))
18    }
19}
20
21impl ReceiptResponse for FoundryTxReceipt {
22    fn contract_address(&self) -> Option<Address> {
23        self.0.contract_address
24    }
25
26    fn status(&self) -> bool {
27        self.0.inner.status()
28    }
29
30    fn block_hash(&self) -> Option<BlockHash> {
31        self.0.block_hash
32    }
33
34    fn block_number(&self) -> Option<u64> {
35        self.0.block_number
36    }
37
38    fn transaction_hash(&self) -> TxHash {
39        self.0.transaction_hash
40    }
41
42    fn transaction_index(&self) -> Option<u64> {
43        self.0.transaction_index()
44    }
45
46    fn gas_used(&self) -> u64 {
47        self.0.gas_used()
48    }
49
50    fn effective_gas_price(&self) -> u128 {
51        self.0.effective_gas_price()
52    }
53
54    fn blob_gas_used(&self) -> Option<u64> {
55        self.0.blob_gas_used()
56    }
57
58    fn blob_gas_price(&self) -> Option<u128> {
59        self.0.blob_gas_price()
60    }
61
62    fn from(&self) -> Address {
63        self.0.from()
64    }
65
66    fn to(&self) -> Option<Address> {
67        self.0.to()
68    }
69
70    fn cumulative_gas_used(&self) -> u64 {
71        self.0.cumulative_gas_used()
72    }
73
74    fn state_root(&self) -> Option<B256> {
75        self.0.state_root()
76    }
77}
78
79impl TryFrom<AnyTransactionReceipt> for FoundryTxReceipt {
80    type Error = ConversionError;
81
82    fn try_from(receipt: AnyTransactionReceipt) -> Result<Self, Self::Error> {
83        let WithOtherFields {
84            inner:
85                TransactionReceipt {
86                    transaction_hash,
87                    transaction_index,
88                    block_hash,
89                    block_number,
90                    gas_used,
91                    contract_address,
92                    effective_gas_price,
93                    from,
94                    to,
95                    blob_gas_price,
96                    blob_gas_used,
97                    inner: AnyReceiptEnvelope { inner: receipt_with_bloom, r#type },
98                },
99            other,
100        } = receipt;
101
102        Ok(Self(WithOtherFields {
103            inner: TransactionReceipt {
104                transaction_hash,
105                transaction_index,
106                block_hash,
107                block_number,
108                gas_used,
109                contract_address,
110                effective_gas_price,
111                from,
112                to,
113                blob_gas_price,
114                blob_gas_used,
115                inner: match r#type {
116                    0x00 => FoundryReceiptEnvelope::Legacy(receipt_with_bloom),
117                    0x01 => FoundryReceiptEnvelope::Eip2930(receipt_with_bloom),
118                    0x02 => FoundryReceiptEnvelope::Eip1559(receipt_with_bloom),
119                    0x03 => FoundryReceiptEnvelope::Eip4844(receipt_with_bloom),
120                    0x04 => FoundryReceiptEnvelope::Eip7702(receipt_with_bloom),
121                    0x7E => {
122                        // Construct the deposit receipt, extracting optional deposit fields
123                        // These fields may not be present in all receipts, so missing/invalid
124                        // values are None
125                        let deposit_nonce = other
126                            .get_deserialized::<U64>("depositNonce")
127                            .transpose()
128                            .ok()
129                            .flatten()
130                            .map(|v| v.to::<u64>());
131                        let deposit_receipt_version = other
132                            .get_deserialized::<U64>("depositReceiptVersion")
133                            .transpose()
134                            .ok()
135                            .flatten()
136                            .map(|v| v.to::<u64>());
137
138                        FoundryReceiptEnvelope::Deposit(OpDepositReceiptWithBloom {
139                            receipt: OpDepositReceipt {
140                                inner: Receipt {
141                                    status: alloy_consensus::Eip658Value::Eip658(
142                                        receipt_with_bloom.status(),
143                                    ),
144                                    cumulative_gas_used: receipt_with_bloom.cumulative_gas_used(),
145                                    logs: receipt_with_bloom.receipt.logs,
146                                },
147                                deposit_nonce,
148                                deposit_receipt_version,
149                            },
150                            logs_bloom: receipt_with_bloom.logs_bloom,
151                        })
152                    }
153                    _ => {
154                        let tx_type = r#type;
155                        return Err(ConversionError::Custom(format!(
156                            "Unknown transaction receipt type: 0x{tx_type:02X}"
157                        )));
158                    }
159                },
160            },
161            other,
162        }))
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    // <https://github.com/foundry-rs/foundry/issues/10852>
171    #[test]
172    fn test_receipt_convert() {
173        let s = r#"{"type":"0x4","status":"0x1","cumulativeGasUsed":"0x903fd1","logs":[{"address":"0x0000d9fcd47bf761e7287d8ee09917d7e2100000","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000234ce51365b9c417171b6dad280f49143e1b0547"],"data":"0x00000000000000000000000000000000000000000000032139b42c3431700000","blockHash":"0xd26b59c1d8b5bfa9362d19eb0da3819dfe0b367987a71f6d30908dd45e0d7a60","blockNumber":"0x159663e","blockTimestamp":"0x68411f7b","transactionHash":"0x17a6af73d1317e69cfc3cac9221bd98261d40f24815850a44dbfbf96652ae52a","transactionIndex":"0x22","logIndex":"0x158","removed":false}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000008100000000000000000000000000000000000000000000000020000200000000000000800000000800000000000000010000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x17a6af73d1317e69cfc3cac9221bd98261d40f24815850a44dbfbf96652ae52a","transactionIndex":"0x22","blockHash":"0xd26b59c1d8b5bfa9362d19eb0da3819dfe0b367987a71f6d30908dd45e0d7a60","blockNumber":"0x159663e","gasUsed":"0x28ee7","effectiveGasPrice":"0x4bf02090","from":"0x234ce51365b9c417171b6dad280f49143e1b0547","to":"0x234ce51365b9c417171b6dad280f49143e1b0547","contractAddress":null}"#;
174        let receipt: AnyTransactionReceipt = serde_json::from_str(s).unwrap();
175        let _converted = FoundryTxReceipt::try_from(receipt).unwrap();
176    }
177}