reth_seismic_primitives/
receipt.rs

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