reth_optimism_primitives/
receipt.rs

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