reth_ethereum_primitives/
receipt.rs

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