reth_optimism_primitives/
receipt.rs

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