reth_ethereum_primitives/
receipt.rs

1use core::fmt::Debug;
2
3use alloc::vec::Vec;
4use alloy_consensus::{
5    Eip2718EncodableReceipt, Eip658Value, ReceiptWithBloom, RlpDecodableReceipt,
6    RlpEncodableReceipt, TxReceipt, TxType, Typed2718,
7};
8use alloy_eips::{
9    eip2718::{Eip2718Error, Eip2718Result, Encodable2718, IsTyped2718},
10    Decodable2718,
11};
12use alloy_primitives::{Bloom, Log, B256};
13use alloy_rlp::{BufMut, Decodable, Encodable, Header};
14use reth_primitives_traits::{proofs::ordered_trie_root_with_encoder, InMemorySize};
15
16/// Helper trait alias with requirements for transaction type generic to be used within [`Receipt`].
17pub trait TxTy:
18    Debug
19    + Copy
20    + Eq
21    + Send
22    + Sync
23    + InMemorySize
24    + Typed2718
25    + TryFrom<u8, Error = Eip2718Error>
26    + Decodable
27    + 'static
28{
29}
30impl<T> TxTy for T where
31    T: Debug
32        + Copy
33        + Eq
34        + Send
35        + Sync
36        + InMemorySize
37        + Typed2718
38        + TryFrom<u8, Error = Eip2718Error>
39        + Decodable
40        + 'static
41{
42}
43
44/// Typed ethereum transaction receipt.
45/// Receipt containing result of transaction execution.
46#[derive(Clone, Debug, PartialEq, Eq, Default)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
49#[cfg_attr(feature = "reth-codec", reth_codecs::add_arbitrary_tests(compact, rlp))]
50pub struct Receipt<T = TxType> {
51    /// Receipt type.
52    pub tx_type: T,
53    /// If transaction is executed successfully.
54    ///
55    /// This is the `statusCode`
56    pub success: bool,
57    /// Gas used
58    pub cumulative_gas_used: u64,
59    /// Log send from contracts.
60    pub logs: Vec<Log>,
61}
62
63impl<T: TxTy> Receipt<T> {
64    /// Returns length of RLP-encoded receipt fields with the given [`Bloom`] without an RLP header.
65    pub fn rlp_encoded_fields_length(&self, bloom: &Bloom) -> usize {
66        self.success.length() +
67            self.cumulative_gas_used.length() +
68            bloom.length() +
69            self.logs.length()
70    }
71
72    /// RLP-encodes receipt fields with the given [`Bloom`] without an RLP header.
73    pub fn rlp_encode_fields(&self, bloom: &Bloom, out: &mut dyn BufMut) {
74        self.success.encode(out);
75        self.cumulative_gas_used.encode(out);
76        bloom.encode(out);
77        self.logs.encode(out);
78    }
79
80    /// Returns RLP header for inner encoding.
81    pub fn rlp_header_inner(&self, bloom: &Bloom) -> Header {
82        Header { list: true, payload_length: self.rlp_encoded_fields_length(bloom) }
83    }
84
85    /// RLP-decodes the receipt from the provided buffer. This does not expect a type byte or
86    /// network header.
87    pub fn rlp_decode_inner(
88        buf: &mut &[u8],
89        tx_type: T,
90    ) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
91        let header = Header::decode(buf)?;
92        if !header.list {
93            return Err(alloy_rlp::Error::UnexpectedString);
94        }
95
96        let remaining = buf.len();
97
98        let success = Decodable::decode(buf)?;
99        let cumulative_gas_used = Decodable::decode(buf)?;
100        let logs_bloom = Decodable::decode(buf)?;
101        let logs = Decodable::decode(buf)?;
102
103        if buf.len() + header.payload_length != remaining {
104            return Err(alloy_rlp::Error::UnexpectedLength);
105        }
106
107        Ok(ReceiptWithBloom {
108            receipt: Self { cumulative_gas_used, tx_type, success, logs },
109            logs_bloom,
110        })
111    }
112
113    /// Calculates the receipt root for a header for the reference type of [Receipt].
114    ///
115    /// NOTE: Prefer `proofs::calculate_receipt_root` if you have log blooms memoized.
116    pub fn calculate_receipt_root_no_memo(receipts: &[Self]) -> B256 {
117        ordered_trie_root_with_encoder(receipts, |r, buf| r.with_bloom_ref().encode_2718(buf))
118    }
119
120    /// Returns length of RLP-encoded receipt fields without the given [`Bloom`] without an RLP
121    /// header
122    pub fn rlp_encoded_fields_length_without_bloom(&self) -> usize {
123        self.success.length() + self.cumulative_gas_used.length() + self.logs.length()
124    }
125
126    /// RLP-encodes receipt fields without the given [`Bloom`] without an RLP header.
127    pub fn rlp_encode_fields_without_bloom(&self, out: &mut dyn BufMut) {
128        self.success.encode(out);
129        self.cumulative_gas_used.encode(out);
130        self.logs.encode(out);
131    }
132
133    /// Returns RLP header for inner encoding.
134    pub fn rlp_header_inner_without_bloom(&self) -> Header {
135        Header { list: true, payload_length: self.rlp_encoded_fields_length_without_bloom() }
136    }
137
138    /// RLP-decodes the receipt from the provided buffer. This does not expect a type byte or
139    /// network header.
140    pub fn rlp_decode_inner_without_bloom(buf: &mut &[u8], tx_type: T) -> alloy_rlp::Result<Self> {
141        let header = Header::decode(buf)?;
142        if !header.list {
143            return Err(alloy_rlp::Error::UnexpectedString);
144        }
145
146        let remaining = buf.len();
147        let success = Decodable::decode(buf)?;
148        let cumulative_gas_used = Decodable::decode(buf)?;
149        let logs = Decodable::decode(buf)?;
150
151        if buf.len() + header.payload_length != remaining {
152            return Err(alloy_rlp::Error::UnexpectedLength);
153        }
154
155        Ok(Self { tx_type, success, cumulative_gas_used, logs })
156    }
157}
158
159impl<T: TxTy> Eip2718EncodableReceipt for Receipt<T> {
160    fn eip2718_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
161        !self.tx_type.is_legacy() as usize + self.rlp_header_inner(bloom).length_with_payload()
162    }
163
164    fn eip2718_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
165        if !self.tx_type.is_legacy() {
166            out.put_u8(self.tx_type.ty());
167        }
168        self.rlp_header_inner(bloom).encode(out);
169        self.rlp_encode_fields(bloom, out);
170    }
171}
172
173impl<T: TxTy> RlpEncodableReceipt for Receipt<T> {
174    fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
175        let mut len = self.eip2718_encoded_length_with_bloom(bloom);
176        if !self.tx_type.is_legacy() {
177            len += Header {
178                list: false,
179                payload_length: self.eip2718_encoded_length_with_bloom(bloom),
180            }
181            .length();
182        }
183
184        len
185    }
186
187    fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
188        if !self.tx_type.is_legacy() {
189            Header { list: false, payload_length: self.eip2718_encoded_length_with_bloom(bloom) }
190                .encode(out);
191        }
192        self.eip2718_encode_with_bloom(bloom, out);
193    }
194}
195
196impl<T: TxTy> RlpDecodableReceipt for Receipt<T> {
197    fn rlp_decode_with_bloom(buf: &mut &[u8]) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
198        let header_buf = &mut &**buf;
199        let header = Header::decode(header_buf)?;
200
201        // Legacy receipt, reuse initial buffer without advancing
202        if header.list {
203            return Self::rlp_decode_inner(buf, T::try_from(0)?)
204        }
205
206        // Otherwise, advance the buffer and try decoding type flag followed by receipt
207        *buf = *header_buf;
208
209        let remaining = buf.len();
210        let tx_type = T::decode(buf)?;
211        let this = Self::rlp_decode_inner(buf, tx_type)?;
212
213        if buf.len() + header.payload_length != remaining {
214            return Err(alloy_rlp::Error::UnexpectedLength);
215        }
216
217        Ok(this)
218    }
219}
220
221impl<T: TxTy> Encodable2718 for Receipt<T> {
222    fn encode_2718_len(&self) -> usize {
223        (!self.tx_type.is_legacy() as usize) +
224            self.rlp_header_inner_without_bloom().length_with_payload()
225    }
226
227    // encode the header
228    fn encode_2718(&self, out: &mut dyn BufMut) {
229        if !self.tx_type.is_legacy() {
230            out.put_u8(self.tx_type.ty());
231        }
232        self.rlp_header_inner_without_bloom().encode(out);
233        self.rlp_encode_fields_without_bloom(out);
234    }
235}
236
237impl<T: TxTy> Decodable2718 for Receipt<T> {
238    fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
239        Ok(Self::rlp_decode_inner_without_bloom(buf, T::try_from(ty)?)?)
240    }
241
242    fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
243        Ok(Self::rlp_decode_inner_without_bloom(buf, T::try_from(0)?)?)
244    }
245}
246
247impl<T: TxTy> Encodable for Receipt<T> {
248    fn encode(&self, out: &mut dyn BufMut) {
249        self.network_encode(out);
250    }
251
252    fn length(&self) -> usize {
253        self.network_len()
254    }
255}
256
257impl<T: TxTy> Decodable for Receipt<T> {
258    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
259        Ok(Self::network_decode(buf)?)
260    }
261}
262
263impl<T: TxTy> TxReceipt for Receipt<T> {
264    type Log = Log;
265
266    fn status_or_post_state(&self) -> Eip658Value {
267        self.success.into()
268    }
269
270    fn status(&self) -> bool {
271        self.success
272    }
273
274    fn bloom(&self) -> Bloom {
275        alloy_primitives::logs_bloom(self.logs())
276    }
277
278    fn cumulative_gas_used(&self) -> u64 {
279        self.cumulative_gas_used
280    }
281
282    fn logs(&self) -> &[Log] {
283        &self.logs
284    }
285
286    fn into_logs(self) -> Vec<Log> {
287        self.logs
288    }
289}
290
291impl<T: TxTy> Typed2718 for Receipt<T> {
292    fn ty(&self) -> u8 {
293        self.tx_type.ty()
294    }
295}
296
297impl<T: TxTy> IsTyped2718 for Receipt<T> {
298    fn is_type(type_id: u8) -> bool {
299        <TxType as IsTyped2718>::is_type(type_id)
300    }
301}
302
303impl<T: TxTy> InMemorySize for Receipt<T> {
304    fn size(&self) -> usize {
305        self.tx_type.size() +
306            core::mem::size_of::<bool>() +
307            core::mem::size_of::<u64>() +
308            self.logs.capacity() * core::mem::size_of::<Log>()
309    }
310}
311
312impl<T> From<alloy_consensus::ReceiptEnvelope<T>> for Receipt<TxType>
313where
314    T: Into<Log>,
315{
316    fn from(value: alloy_consensus::ReceiptEnvelope<T>) -> Self {
317        let value = value.into_primitives_receipt();
318        Self {
319            tx_type: value.tx_type(),
320            success: value.is_success(),
321            cumulative_gas_used: value.cumulative_gas_used(),
322            logs: value.into_logs(),
323        }
324    }
325}
326
327impl<T> From<Receipt<T>> for alloy_consensus::Receipt<Log> {
328    fn from(value: Receipt<T>) -> Self {
329        Self {
330            status: value.success.into(),
331            cumulative_gas_used: value.cumulative_gas_used,
332            logs: value.logs,
333        }
334    }
335}
336
337impl From<Receipt<TxType>> for alloy_consensus::ReceiptEnvelope<Log> {
338    fn from(value: Receipt<TxType>) -> Self {
339        let tx_type = value.tx_type;
340        let receipt = value.into_with_bloom().map_receipt(Into::into);
341        match tx_type {
342            TxType::Legacy => Self::Legacy(receipt),
343            TxType::Eip2930 => Self::Eip2930(receipt),
344            TxType::Eip1559 => Self::Eip1559(receipt),
345            TxType::Eip4844 => Self::Eip4844(receipt),
346            TxType::Eip7702 => Self::Eip7702(receipt),
347        }
348    }
349}
350
351#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
352pub(super) mod serde_bincode_compat {
353    use alloc::{borrow::Cow, vec::Vec};
354    use alloy_consensus::TxType;
355    use alloy_eips::eip2718::Eip2718Error;
356    use alloy_primitives::{Log, U8};
357    use core::fmt::Debug;
358    use serde::{Deserialize, Deserializer, Serialize, Serializer};
359    use serde_with::{DeserializeAs, SerializeAs};
360
361    /// Bincode-compatible [`super::Receipt`] serde implementation.
362    ///
363    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
364    /// ```rust
365    /// use alloy_consensus::TxType;
366    /// use reth_ethereum_primitives::{serde_bincode_compat, Receipt};
367    /// use serde::{de::DeserializeOwned, Deserialize, Serialize};
368    /// use serde_with::serde_as;
369    ///
370    /// #[serde_as]
371    /// #[derive(Serialize, Deserialize)]
372    /// struct Data {
373    ///     #[serde_as(as = "serde_bincode_compat::Receipt<'_>")]
374    ///     receipt: Receipt<TxType>,
375    /// }
376    /// ```
377    #[derive(Debug, Serialize, Deserialize)]
378    #[serde(bound(deserialize = "T: TryFrom<u8, Error = Eip2718Error>"))]
379    pub struct Receipt<'a, T = TxType> {
380        /// Receipt type.
381        #[serde(deserialize_with = "deserde_txtype")]
382        pub tx_type: T,
383        /// If transaction is executed successfully.
384        ///
385        /// This is the `statusCode`
386        pub success: bool,
387        /// Gas used
388        pub cumulative_gas_used: u64,
389        /// Log send from contracts.
390        pub logs: Cow<'a, Vec<Log>>,
391    }
392
393    /// Ensures that txtype is deserialized symmetrically as U8
394    fn deserde_txtype<'de, D, T>(deserializer: D) -> Result<T, D::Error>
395    where
396        D: Deserializer<'de>,
397        T: TryFrom<u8, Error = Eip2718Error>,
398    {
399        U8::deserialize(deserializer)?.to::<u8>().try_into().map_err(serde::de::Error::custom)
400    }
401
402    impl<'a, T: Copy> From<&'a super::Receipt<T>> for Receipt<'a, T> {
403        fn from(value: &'a super::Receipt<T>) -> Self {
404            Self {
405                tx_type: value.tx_type,
406                success: value.success,
407                cumulative_gas_used: value.cumulative_gas_used,
408                logs: Cow::Borrowed(&value.logs),
409            }
410        }
411    }
412
413    impl<'a, T> From<Receipt<'a, T>> for super::Receipt<T> {
414        fn from(value: Receipt<'a, T>) -> Self {
415            Self {
416                tx_type: value.tx_type,
417                success: value.success,
418                cumulative_gas_used: value.cumulative_gas_used,
419                logs: value.logs.into_owned(),
420            }
421        }
422    }
423
424    impl<T: Copy + Serialize> SerializeAs<super::Receipt<T>> for Receipt<'_, T> {
425        fn serialize_as<S>(source: &super::Receipt<T>, serializer: S) -> Result<S::Ok, S::Error>
426        where
427            S: Serializer,
428        {
429            Receipt::<'_>::from(source).serialize(serializer)
430        }
431    }
432
433    impl<'de, T: TryFrom<u8, Error = Eip2718Error>> DeserializeAs<'de, super::Receipt<T>>
434        for Receipt<'de, T>
435    {
436        fn deserialize_as<D>(deserializer: D) -> Result<super::Receipt<T>, D::Error>
437        where
438            D: Deserializer<'de>,
439        {
440            Receipt::<'_, T>::deserialize(deserializer).map(Into::into)
441        }
442    }
443
444    impl<T> reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat for super::Receipt<T>
445    where
446        T: Copy + Serialize + TryFrom<u8, Error = Eip2718Error> + Debug + 'static,
447    {
448        type BincodeRepr<'a> = Receipt<'a, T>;
449
450        fn as_repr(&self) -> Self::BincodeRepr<'_> {
451            self.into()
452        }
453
454        fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
455            repr.into()
456        }
457    }
458
459    #[cfg(test)]
460    mod tests {
461        use crate::{receipt::serde_bincode_compat, Receipt};
462        use alloy_consensus::TxType;
463        use arbitrary::Arbitrary;
464        use rand::Rng;
465        use serde_with::serde_as;
466
467        #[test]
468        fn test_receipt_bincode_roundtrip() {
469            #[serde_as]
470            #[derive(Debug, PartialEq, Eq)]
471            #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
472            struct Data {
473                #[serde_as(as = "serde_bincode_compat::Receipt<'_, TxType>")]
474                receipt: Receipt<TxType>,
475            }
476
477            let mut bytes = [0u8; 1024];
478            rand::rng().fill(bytes.as_mut_slice());
479            let data = Data {
480                receipt: Receipt::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(),
481            };
482            let encoded = bincode::serialize(&data).unwrap();
483            let decoded: Data = bincode::deserialize(&encoded).unwrap();
484            assert_eq!(decoded, data);
485        }
486    }
487}
488
489#[cfg(feature = "reth-codec")]
490mod compact {
491    use super::*;
492    use reth_codecs::{
493        Compact,
494        __private::{modular_bitfield::prelude::*, Buf},
495    };
496
497    impl Receipt {
498        #[doc = "Used bytes by [`ReceiptFlags`]"]
499        pub const fn bitflag_encoded_bytes() -> usize {
500            1u8 as usize
501        }
502        #[doc = "Unused bits for new fields by [`ReceiptFlags`]"]
503        pub const fn bitflag_unused_bits() -> usize {
504            0u8 as usize
505        }
506    }
507
508    #[allow(non_snake_case, unused_parens)]
509    mod flags {
510        use super::*;
511
512        #[doc = "Fieldset that facilitates compacting the parent type. Used bytes: 1 | Unused bits: 0"]
513        #[bitfield]
514        #[derive(Clone, Copy, Debug, Default)]
515        pub struct ReceiptFlags {
516            pub tx_type_len: B2,
517            pub success_len: B1,
518            pub cumulative_gas_used_len: B4,
519            pub __zstd: B1,
520        }
521
522        impl ReceiptFlags {
523            #[doc = r" Deserializes this fieldset and returns it, alongside the original slice in an advanced position."]
524            pub fn from(mut buf: &[u8]) -> (Self, &[u8]) {
525                (Self::from_bytes([buf.get_u8()]), buf)
526            }
527        }
528    }
529
530    pub use flags::ReceiptFlags;
531
532    impl<T: Compact> Compact for Receipt<T> {
533        fn to_compact<B>(&self, buf: &mut B) -> usize
534        where
535            B: reth_codecs::__private::bytes::BufMut + AsMut<[u8]>,
536        {
537            let mut flags = ReceiptFlags::default();
538            let mut total_length = 0;
539            let mut buffer = reth_codecs::__private::bytes::BytesMut::new();
540
541            let tx_type_len = self.tx_type.to_compact(&mut buffer);
542            flags.set_tx_type_len(tx_type_len as u8);
543            let success_len = self.success.to_compact(&mut buffer);
544            flags.set_success_len(success_len as u8);
545            let cumulative_gas_used_len = self.cumulative_gas_used.to_compact(&mut buffer);
546            flags.set_cumulative_gas_used_len(cumulative_gas_used_len as u8);
547            self.logs.to_compact(&mut buffer);
548
549            let zstd = buffer.len() > 7;
550            if zstd {
551                flags.set___zstd(1);
552            }
553
554            let flags = flags.into_bytes();
555            total_length += flags.len() + buffer.len();
556            buf.put_slice(&flags);
557            if zstd {
558                reth_zstd_compressors::RECEIPT_COMPRESSOR.with(|compressor| {
559                    let compressed =
560                        compressor.borrow_mut().compress(&buffer).expect("Failed to compress.");
561                    buf.put(compressed.as_slice());
562                });
563            } else {
564                buf.put(buffer);
565            }
566            total_length
567        }
568
569        fn from_compact(buf: &[u8], _len: usize) -> (Self, &[u8]) {
570            let (flags, mut buf) = ReceiptFlags::from(buf);
571            if flags.__zstd() != 0 {
572                reth_zstd_compressors::RECEIPT_DECOMPRESSOR.with(|decompressor| {
573                    let decompressor = &mut decompressor.borrow_mut();
574                    let decompressed = decompressor.decompress(buf);
575                    let original_buf = buf;
576                    let mut buf: &[u8] = decompressed;
577                    let (tx_type, new_buf) = T::from_compact(buf, flags.tx_type_len() as usize);
578                    buf = new_buf;
579                    let (success, new_buf) = bool::from_compact(buf, flags.success_len() as usize);
580                    buf = new_buf;
581                    let (cumulative_gas_used, new_buf) =
582                        u64::from_compact(buf, flags.cumulative_gas_used_len() as usize);
583                    buf = new_buf;
584                    let (logs, _) = Vec::from_compact(buf, buf.len());
585                    (Self { tx_type, success, cumulative_gas_used, logs }, original_buf)
586                })
587            } else {
588                let (tx_type, new_buf) = T::from_compact(buf, flags.tx_type_len() as usize);
589                buf = new_buf;
590                let (success, new_buf) = bool::from_compact(buf, flags.success_len() as usize);
591                buf = new_buf;
592                let (cumulative_gas_used, new_buf) =
593                    u64::from_compact(buf, flags.cumulative_gas_used_len() as usize);
594                buf = new_buf;
595                let (logs, new_buf) = Vec::from_compact(buf, buf.len());
596                buf = new_buf;
597                let obj = Self { tx_type, success, cumulative_gas_used, logs };
598                (obj, buf)
599            }
600        }
601    }
602}
603
604#[cfg(feature = "reth-codec")]
605pub use compact::*;
606
607#[cfg(test)]
608mod tests {
609    use super::*;
610    use crate::TransactionSigned;
611    use alloy_eips::eip2718::Encodable2718;
612    use alloy_primitives::{
613        address, b256, bloom, bytes, hex_literal::hex, Address, Bytes, Log, LogData,
614    };
615    use alloy_rlp::Decodable;
616    use reth_codecs::Compact;
617    use reth_primitives_traits::proofs::{
618        calculate_receipt_root, calculate_transaction_root, calculate_withdrawals_root,
619    };
620
621    /// Ethereum full block.
622    ///
623    /// Withdrawals can be optionally included at the end of the RLP encoded message.
624    pub(crate) type Block<T = TransactionSigned> = alloy_consensus::Block<T>;
625
626    #[test]
627    fn test_decode_receipt() {
628        reth_codecs::test_utils::test_decode::<Receipt<TxType>>(&hex!(
629            "c428b52ffd23fc42696156b10200f034792b6a94c3850215c2fef7aea361a0c31b79d9a32652eefc0d4e2e730036061cff7344b6fc6132b50cda0ed810a991ae58ef013150c12b2522533cb3b3a8b19b7786a8b5ff1d3cdc84225e22b02def168c8858df"
630        ));
631    }
632
633    // Test vector from: https://eips.ethereum.org/EIPS/eip-2481
634    #[test]
635    fn encode_legacy_receipt() {
636        let expected = hex!(
637            "f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"
638        );
639
640        let mut data = Vec::with_capacity(expected.length());
641        let receipt = ReceiptWithBloom {
642            receipt: Receipt {
643                tx_type: TxType::Legacy,
644                cumulative_gas_used: 0x1u64,
645                logs: vec![Log::new_unchecked(
646                    address!("0x0000000000000000000000000000000000000011"),
647                    vec![
648                        b256!("0x000000000000000000000000000000000000000000000000000000000000dead"),
649                        b256!("0x000000000000000000000000000000000000000000000000000000000000beef"),
650                    ],
651                    bytes!("0100ff"),
652                )],
653                success: false,
654            },
655            logs_bloom: [0; 256].into(),
656        };
657
658        receipt.encode(&mut data);
659
660        // check that the rlp length equals the length of the expected rlp
661        assert_eq!(receipt.length(), expected.len());
662        assert_eq!(data, expected);
663    }
664
665    // Test vector from: https://eips.ethereum.org/EIPS/eip-2481
666    #[test]
667    fn decode_legacy_receipt() {
668        let data = hex!(
669            "f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"
670        );
671
672        // EIP658Receipt
673        let expected = ReceiptWithBloom {
674            receipt: Receipt {
675                tx_type: TxType::Legacy,
676                cumulative_gas_used: 0x1u64,
677                logs: vec![Log::new_unchecked(
678                    address!("0x0000000000000000000000000000000000000011"),
679                    vec![
680                        b256!("0x000000000000000000000000000000000000000000000000000000000000dead"),
681                        b256!("0x000000000000000000000000000000000000000000000000000000000000beef"),
682                    ],
683                    bytes!("0100ff"),
684                )],
685                success: false,
686            },
687            logs_bloom: [0; 256].into(),
688        };
689
690        let receipt = ReceiptWithBloom::decode(&mut &data[..]).unwrap();
691        assert_eq!(receipt, expected);
692    }
693
694    #[test]
695    fn gigantic_receipt() {
696        let receipt = Receipt {
697            cumulative_gas_used: 16747627,
698            success: true,
699            tx_type: TxType::Legacy,
700            logs: vec![
701                Log::new_unchecked(
702                    address!("0x4bf56695415f725e43c3e04354b604bcfb6dfb6e"),
703                    vec![b256!(
704                        "0xc69dc3d7ebff79e41f525be431d5cd3cc08f80eaf0f7819054a726eeb7086eb9"
705                    )],
706                    Bytes::from(vec![1; 0xffffff]),
707                ),
708                Log::new_unchecked(
709                    address!("0xfaca325c86bf9c2d5b413cd7b90b209be92229c2"),
710                    vec![b256!(
711                        "0x8cca58667b1e9ffa004720ac99a3d61a138181963b294d270d91c53d36402ae2"
712                    )],
713                    Bytes::from(vec![1; 0xffffff]),
714                ),
715            ],
716        };
717
718        let mut data = vec![];
719        receipt.to_compact(&mut data);
720        let (decoded, _) = Receipt::<TxType>::from_compact(&data[..], data.len());
721        assert_eq!(decoded, receipt);
722    }
723
724    #[test]
725    fn test_encode_2718_length() {
726        let receipt = ReceiptWithBloom {
727            receipt: Receipt {
728                tx_type: TxType::Eip1559,
729                success: true,
730                cumulative_gas_used: 21000,
731                logs: vec![],
732            },
733            logs_bloom: Bloom::default(),
734        };
735
736        let encoded = receipt.encoded_2718();
737        assert_eq!(
738            encoded.len(),
739            receipt.encode_2718_len(),
740            "Encoded length should match the actual encoded data length"
741        );
742
743        // Test for legacy receipt as well
744        let legacy_receipt = ReceiptWithBloom {
745            receipt: Receipt {
746                tx_type: TxType::Legacy,
747                success: true,
748                cumulative_gas_used: 21000,
749                logs: vec![],
750            },
751            logs_bloom: Bloom::default(),
752        };
753
754        let legacy_encoded = legacy_receipt.encoded_2718();
755        assert_eq!(
756            legacy_encoded.len(),
757            legacy_receipt.encode_2718_len(),
758            "Encoded length for legacy receipt should match the actual encoded data length"
759        );
760    }
761
762    #[test]
763    fn check_transaction_root() {
764        let data = &hex!(
765            "f90262f901f9a092230ce5476ae868e98c7979cfc165a93f8b6ad1922acf2df62e340916efd49da01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa02307107a867056ca33b5087e77c4174f47625e48fb49f1c70ced34890ddd88f3a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba0c598f69a5674cae9337261b669970e24abc0b46e6d284372a239ec8ccbf20b0ab901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8618203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0"
766        );
767        let block_rlp = &mut data.as_slice();
768        let block: Block = Block::decode(block_rlp).unwrap();
769
770        let tx_root = calculate_transaction_root(&block.body.transactions);
771        assert_eq!(block.transactions_root, tx_root, "Must be the same");
772    }
773
774    #[test]
775    fn check_withdrawals_root() {
776        // Single withdrawal, amount 0
777        // https://github.com/ethereum/tests/blob/9760400e667eba241265016b02644ef62ab55de2/BlockchainTests/EIPTests/bc4895-withdrawals/amountIs0.json
778        let data = &hex!(
779            "f90238f90219a0151934ad9b654c50197f37018ee5ee9bb922dec0a1b5e24a6d679cb111cdb107a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0046119afb1ab36aaa8f66088677ed96cd62762f6d3e65642898e189fbe702d51a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff8082079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a048a703da164234812273ea083e4ec3d09d028300cd325b46a6a75402e5a7ab95c0c0d9d8808094c94f5374fce5edbc8e2a8697c15331677e6ebf0b80"
780        );
781        let block: Block = Block::decode(&mut data.as_slice()).unwrap();
782        assert!(block.body.withdrawals.is_some());
783        let withdrawals = block.body.withdrawals.as_ref().unwrap();
784        assert_eq!(withdrawals.len(), 1);
785        let withdrawals_root = calculate_withdrawals_root(withdrawals);
786        assert_eq!(block.withdrawals_root, Some(withdrawals_root));
787
788        // 4 withdrawals, identical indices
789        // https://github.com/ethereum/tests/blob/9760400e667eba241265016b02644ef62ab55de2/BlockchainTests/EIPTests/bc4895-withdrawals/twoIdenticalIndex.json
790        let data = &hex!(
791            "f9028cf90219a0151934ad9b654c50197f37018ee5ee9bb922dec0a1b5e24a6d679cb111cdb107a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0ccf7b62d616c2ad7af862d67b9dcd2119a90cebbff8c3cd1e5d7fc99f8755774a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff8082079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a0a95b9a7b58a6b3cb4001eb0be67951c5517141cb0183a255b5cae027a7b10b36c0c0f86cda808094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da028094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da018094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da028094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710"
792        );
793        let block: Block = Block::decode(&mut data.as_slice()).unwrap();
794        assert!(block.body.withdrawals.is_some());
795        let withdrawals = block.body.withdrawals.as_ref().unwrap();
796        assert_eq!(withdrawals.len(), 4);
797        let withdrawals_root = calculate_withdrawals_root(withdrawals);
798        assert_eq!(block.withdrawals_root, Some(withdrawals_root));
799    }
800    #[test]
801    fn check_receipt_root_optimism() {
802        use alloy_consensus::ReceiptWithBloom;
803
804        let logs = vec![Log {
805            address: Address::ZERO,
806            data: LogData::new_unchecked(vec![], Default::default()),
807        }];
808        let bloom = bloom!(
809            "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"
810        );
811        let receipt = ReceiptWithBloom {
812            receipt: Receipt {
813                tx_type: TxType::Eip2930,
814                success: true,
815                cumulative_gas_used: 102068,
816                logs,
817            },
818            logs_bloom: bloom,
819        };
820        let receipt = vec![receipt];
821        let root = calculate_receipt_root(&receipt);
822        assert_eq!(
823            root,
824            b256!("0xfe70ae4a136d98944951b2123859698d59ad251a381abc9960fa81cae3d0d4a0")
825        );
826    }
827}