reth_optimism_primitives/
receipt.rs

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