alloy_consensus/receipt/
mod.rs

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