Skip to main content

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