alloy_consensus/receipt/
mod.rs

1use alloc::vec::Vec;
2use alloy_primitives::Bloom;
3use alloy_rlp::BufMut;
4use core::fmt;
5
6mod envelope;
7pub use envelope::ReceiptEnvelope;
8
9mod receipts;
10pub use receipts::{Receipt, ReceiptWithBloom, Receipts};
11
12mod status;
13pub use status::Eip658Value;
14
15use alloy_eips::{eip2718::Eip2718Result, Typed2718};
16
17/// Bincode-compatible serde implementations for receipt types.
18#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
19pub(crate) mod serde_bincode_compat {
20    pub use super::{envelope::serde_bincode_compat::*, receipts::serde_bincode_compat::*};
21}
22
23/// Receipt is the result of a transaction execution.
24#[doc(alias = "TransactionReceipt")]
25#[auto_impl::auto_impl(&, Arc)]
26pub trait TxReceipt: Clone + fmt::Debug + PartialEq + Eq + Send + Sync {
27    /// The associated log type.
28    type Log;
29
30    /// Returns the status or post state of the transaction.
31    ///
32    /// ## Note
33    ///
34    /// Use this method instead of [`TxReceipt::status`] when the transaction
35    /// is pre-[EIP-658].
36    ///
37    /// [EIP-658]: https://eips.ethereum.org/EIPS/eip-658
38    fn status_or_post_state(&self) -> Eip658Value;
39
40    /// Returns true if the transaction was successful OR if the transaction is
41    /// pre-[EIP-658]. Results for transactions before [EIP-658] are not
42    /// reliable.
43    ///
44    /// ## Note
45    ///
46    /// Caution must be taken when using this method for deep-historical
47    /// receipts, as it may not accurately reflect the status of the
48    /// transaction. The transaction status is not knowable from the receipt
49    /// for transactions before [EIP-658].
50    ///
51    /// This can be handled using [`TxReceipt::status_or_post_state`].
52    ///
53    /// [EIP-658]: https://eips.ethereum.org/EIPS/eip-658
54    fn status(&self) -> bool;
55
56    /// Returns the bloom filter for the logs in the receipt. This operation
57    /// may be expensive.
58    fn bloom(&self) -> Bloom;
59
60    /// Returns the bloom filter for the logs in the receipt, if it is cheap to
61    /// compute.
62    fn bloom_cheap(&self) -> Option<Bloom> {
63        None
64    }
65
66    /// Returns [`ReceiptWithBloom`] with the computed bloom filter [`Self::bloom`] and a reference
67    /// to the receipt.
68    #[auto_impl(keep_default_for(&, Arc))]
69    fn with_bloom_ref(&self) -> ReceiptWithBloom<&Self> {
70        ReceiptWithBloom { logs_bloom: self.bloom(), receipt: self }
71    }
72
73    /// Consumes the type and converts it into [`ReceiptWithBloom`] with the computed bloom filter
74    /// [`Self::bloom`] and the receipt.
75    #[auto_impl(keep_default_for(&, Arc))]
76    fn into_with_bloom(self) -> ReceiptWithBloom<Self> {
77        ReceiptWithBloom { logs_bloom: self.bloom(), receipt: self }
78    }
79
80    /// Consumes the type and converts it into [`ReceiptWithBloom`] with the given bloom filter.
81    #[auto_impl(keep_default_for(&, Arc))]
82    fn into_with_bloom_unchecked(self, logs_bloom: Bloom) -> ReceiptWithBloom<Self> {
83        ReceiptWithBloom { logs_bloom, receipt: self }
84    }
85
86    /// Returns the cumulative gas used in the block after this transaction was executed.
87    fn cumulative_gas_used(&self) -> u64;
88
89    /// Returns the logs emitted by this transaction.
90    fn logs(&self) -> &[Self::Log];
91
92    /// Consumes the type and returns the logs emitted by this transaction as a vector.
93    #[auto_impl(keep_default_for(&, Arc))]
94    fn into_logs(self) -> Vec<Self::Log>
95    where
96        Self::Log: Clone,
97    {
98        self.logs().to_vec()
99    }
100}
101
102/// Receipt type that knows how to encode itself with a [`Bloom`] value.
103#[auto_impl::auto_impl(&)]
104pub trait RlpEncodableReceipt {
105    /// Returns the length of the receipt payload with the provided bloom filter.
106    fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize;
107
108    /// RLP encodes the receipt with the provided bloom filter.
109    fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut);
110}
111
112/// Receipt type that knows how to decode itself with a [`Bloom`] value.
113pub trait RlpDecodableReceipt: Sized {
114    /// RLP decodes receipt and [`Bloom`] into [`ReceiptWithBloom`] instance.
115    fn rlp_decode_with_bloom(buf: &mut &[u8]) -> alloy_rlp::Result<ReceiptWithBloom<Self>>;
116}
117
118/// Receipt type that knows its EIP-2718 encoding.
119///
120/// Main consumer of this trait is [`ReceiptWithBloom`]. It is expected that [`RlpEncodableReceipt`]
121/// implementation for this type produces network encoding which is used by [`alloy_rlp::Encodable`]
122/// implementation for [`ReceiptWithBloom`].
123#[auto_impl::auto_impl(&)]
124pub trait Eip2718EncodableReceipt: RlpEncodableReceipt + Typed2718 {
125    /// EIP-2718 encoded length with the provided bloom filter.
126    fn eip2718_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize;
127
128    /// EIP-2718 encodes the receipt with the provided bloom filter.
129    fn eip2718_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut);
130}
131
132/// Receipt type that knows how to decode itself along with bloom from EIP-2718 format.
133///
134/// This is used to support [`alloy_eips::eip2718::Decodable2718`] implementation for
135/// [`ReceiptWithBloom`].
136pub trait Eip2718DecodableReceipt: Sized {
137    /// EIP-2718 decodes the receipt and bloom from the buffer.
138    fn typed_decode_with_bloom(ty: u8, buf: &mut &[u8]) -> Eip2718Result<ReceiptWithBloom<Self>>;
139
140    /// EIP-2718 decodes the receipt and bloom from the buffer.
141    fn fallback_decode_with_bloom(buf: &mut &[u8]) -> Eip2718Result<ReceiptWithBloom<Self>>;
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147    use alloy_eips::eip2718::Encodable2718;
148    use alloy_primitives::{address, b256, bytes, hex, Log, LogData};
149    use alloy_rlp::{Decodable, Encodable};
150
151    // Test vector from: https://eips.ethereum.org/EIPS/eip-2481
152    #[test]
153    fn encode_legacy_receipt() {
154        let expected = hex!("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff");
155
156        let mut data = vec![];
157        let receipt =
158            ReceiptEnvelope::Legacy(ReceiptWithBloom {
159                receipt: Receipt {
160                    cumulative_gas_used: 0x1,
161                    logs: vec![Log {
162                        address: address!("0000000000000000000000000000000000000011"),
163                        data: LogData::new_unchecked(
164                            vec![
165                    b256!("000000000000000000000000000000000000000000000000000000000000dead"),
166                    b256!("000000000000000000000000000000000000000000000000000000000000beef"),
167                ],
168                            bytes!("0100ff"),
169                        ),
170                    }],
171                    status: false.into(),
172                },
173                logs_bloom: [0; 256].into(),
174            });
175
176        receipt.network_encode(&mut data);
177
178        // check that the rlp length equals the length of the expected rlp
179        assert_eq!(receipt.length(), expected.len());
180        assert_eq!(data, expected);
181    }
182
183    // Test vector from: https://eips.ethereum.org/EIPS/eip-2481
184    #[test]
185    fn decode_legacy_receipt() {
186        let data = hex!("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff");
187
188        // EIP658Receipt
189        let expected =
190            ReceiptWithBloom {
191                receipt: Receipt {
192                    cumulative_gas_used: 0x1,
193                    logs: vec![Log {
194                        address: address!("0000000000000000000000000000000000000011"),
195                        data: LogData::new_unchecked(
196                            vec![
197                        b256!("000000000000000000000000000000000000000000000000000000000000dead"),
198                        b256!("000000000000000000000000000000000000000000000000000000000000beef"),
199                    ],
200                            bytes!("0100ff"),
201                        ),
202                    }],
203                    status: false.into(),
204                },
205                logs_bloom: [0; 256].into(),
206            };
207
208        let receipt = ReceiptWithBloom::decode(&mut &data[..]).unwrap();
209        assert_eq!(receipt, expected);
210    }
211
212    #[test]
213    fn gigantic_receipt() {
214        let receipt = Receipt {
215            cumulative_gas_used: 16747627,
216            status: true.into(),
217            logs: vec![
218                Log {
219                    address: address!("4bf56695415f725e43c3e04354b604bcfb6dfb6e"),
220                    data: LogData::new_unchecked(
221                        vec![b256!(
222                            "c69dc3d7ebff79e41f525be431d5cd3cc08f80eaf0f7819054a726eeb7086eb9"
223                        )],
224                        vec![1; 0xffffff].into(),
225                    ),
226                },
227                Log {
228                    address: address!("faca325c86bf9c2d5b413cd7b90b209be92229c2"),
229                    data: LogData::new_unchecked(
230                        vec![b256!(
231                            "8cca58667b1e9ffa004720ac99a3d61a138181963b294d270d91c53d36402ae2"
232                        )],
233                        vec![1; 0xffffff].into(),
234                    ),
235                },
236            ],
237        }
238        .with_bloom();
239
240        let len = receipt.length();
241        let mut data = Vec::with_capacity(receipt.length());
242
243        receipt.encode(&mut data);
244        assert_eq!(data.len(), len);
245        let decoded = ReceiptWithBloom::decode(&mut &data[..]).unwrap();
246
247        // receipt.clone().to_compact(&mut data);
248        // let (decoded, _) = Receipt::from_compact(&data[..], data.len());
249        assert_eq!(decoded, receipt);
250    }
251
252    #[test]
253    fn can_encode_by_reference() {
254        let receipt: Receipt =
255            Receipt { cumulative_gas_used: 16747627, status: true.into(), logs: vec![] };
256
257        let encoded_ref = alloy_rlp::encode(&ReceiptWithBloom {
258            receipt: &receipt,
259            logs_bloom: receipt.bloom_slow(),
260        });
261        let encoded =
262            alloy_rlp::encode(&ReceiptWithBloom { logs_bloom: receipt.bloom_slow(), receipt });
263
264        assert_eq!(encoded, encoded_ref);
265    }
266}