arb_primitives/
receipt.rs

1use alloc::vec::Vec;
2
3use alloy_consensus::{
4    Eip2718EncodableReceipt, Eip658Value, Receipt as AlloyReceipt, TxReceipt, Typed2718,
5};
6use alloy_eips::{Decodable2718, Encodable2718};
7use alloy_primitives::{Bloom, Log};
8use alloy_rlp::{Decodable, Encodable};
9use arb_alloy_consensus::tx::ArbTxType;
10use reth_primitives_traits::InMemorySize;
11
12use crate::multigas::MultiGas;
13
14/// Arbitrum receipt: wraps the per-type receipt kind with L1 gas metadata.
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct ArbReceipt {
17    pub kind: ArbReceiptKind,
18    /// Gas units used for L1 calldata posting (poster gas).
19    /// Populated by the block executor after receipt construction.
20    pub gas_used_for_l1: u64,
21    /// Multi-dimensional gas usage breakdown.
22    /// Populated when multi-gas tracking is enabled (ArbOS v60+).
23    pub multi_gas_used: MultiGas,
24}
25
26impl ArbReceipt {
27    /// Create a new receipt with no L1 gas usage (filled in later).
28    pub fn new(kind: ArbReceiptKind) -> Self {
29        Self {
30            kind,
31            gas_used_for_l1: 0,
32            multi_gas_used: MultiGas::zero(),
33        }
34    }
35
36    pub fn with_gas_used_for_l1(mut self, gas: u64) -> Self {
37        self.gas_used_for_l1 = gas;
38        self
39    }
40}
41
42/// Trait for setting Arbitrum-specific fields on a receipt after construction.
43pub trait SetArbReceiptFields {
44    fn set_gas_used_for_l1(&mut self, gas: u64);
45    fn set_multi_gas_used(&mut self, multi_gas: MultiGas);
46}
47
48impl SetArbReceiptFields for ArbReceipt {
49    fn set_gas_used_for_l1(&mut self, gas: u64) {
50        self.gas_used_for_l1 = gas;
51    }
52
53    fn set_multi_gas_used(&mut self, multi_gas: MultiGas) {
54        self.multi_gas_used = multi_gas;
55    }
56}
57
58/// Per-type receipt variants matching the Arbitrum transaction type.
59#[derive(Clone, Debug, PartialEq, Eq)]
60pub enum ArbReceiptKind {
61    Legacy(AlloyReceipt),
62    Eip1559(AlloyReceipt),
63    Eip2930(AlloyReceipt),
64    Eip7702(AlloyReceipt),
65    Deposit(ArbDepositReceipt),
66    Unsigned(AlloyReceipt),
67    Contract(AlloyReceipt),
68    Retry(AlloyReceipt),
69    SubmitRetryable(AlloyReceipt),
70    Internal(AlloyReceipt),
71}
72
73/// Deposit receipts always succeed with no gas and no logs.
74#[derive(Clone, Debug, PartialEq, Eq, Default)]
75pub struct ArbDepositReceipt;
76
77// ---------------------------------------------------------------------------
78// ArbReceiptKind — inherent methods (encoding internals)
79// ---------------------------------------------------------------------------
80
81impl ArbReceiptKind {
82    pub const fn arb_tx_type(&self) -> ArbTxType {
83        match self {
84            Self::Legacy(_) | Self::Eip2930(_) | Self::Eip1559(_) | Self::Eip7702(_) => {
85                ArbTxType::ArbitrumLegacyTx
86            }
87            Self::Deposit(_) => ArbTxType::ArbitrumDepositTx,
88            Self::Unsigned(_) => ArbTxType::ArbitrumUnsignedTx,
89            Self::Contract(_) => ArbTxType::ArbitrumContractTx,
90            Self::Retry(_) => ArbTxType::ArbitrumRetryTx,
91            Self::SubmitRetryable(_) => ArbTxType::ArbitrumSubmitRetryableTx,
92            Self::Internal(_) => ArbTxType::ArbitrumInternalTx,
93        }
94    }
95
96    pub const fn as_receipt(&self) -> &AlloyReceipt {
97        match self {
98            Self::Legacy(r)
99            | Self::Eip2930(r)
100            | Self::Eip1559(r)
101            | Self::Eip7702(r)
102            | Self::Unsigned(r)
103            | Self::Contract(r)
104            | Self::Retry(r)
105            | Self::SubmitRetryable(r)
106            | Self::Internal(r) => r,
107            Self::Deposit(_) => unreachable!(),
108        }
109    }
110
111    fn rlp_encoded_fields_length(&self, bloom: &Bloom) -> usize {
112        match self {
113            Self::Legacy(r)
114            | Self::Eip2930(r)
115            | Self::Eip1559(r)
116            | Self::Eip7702(r)
117            | Self::Unsigned(r)
118            | Self::Contract(r)
119            | Self::Retry(r)
120            | Self::SubmitRetryable(r)
121            | Self::Internal(r) => r.rlp_encoded_fields_length_with_bloom(bloom),
122            Self::Deposit(_) => {
123                Eip658Value::Eip658(true).length()
124                    + 0u64.length()
125                    + bloom.length()
126                    + Vec::<Log>::new().length()
127            }
128        }
129    }
130
131    fn rlp_encode_fields(&self, bloom: &Bloom, out: &mut dyn alloy_rlp::bytes::BufMut) {
132        match self {
133            Self::Legacy(r)
134            | Self::Eip2930(r)
135            | Self::Eip1559(r)
136            | Self::Eip7702(r)
137            | Self::Unsigned(r)
138            | Self::Contract(r)
139            | Self::Retry(r)
140            | Self::SubmitRetryable(r)
141            | Self::Internal(r) => r.rlp_encode_fields_with_bloom(bloom, out),
142            Self::Deposit(_) => {
143                Eip658Value::Eip658(true).encode(out);
144                (0u64).encode(out);
145                bloom.encode(out);
146                let logs: Vec<Log> = Vec::new();
147                logs.encode(out);
148            }
149        }
150    }
151
152    fn rlp_header_inner(&self, bloom: &Bloom) -> alloy_rlp::Header {
153        alloy_rlp::Header {
154            list: true,
155            payload_length: self.rlp_encoded_fields_length(bloom),
156        }
157    }
158
159    fn rlp_encode_fields_without_bloom(&self, out: &mut dyn alloy_rlp::bytes::BufMut) {
160        match self {
161            Self::Legacy(r)
162            | Self::Eip2930(r)
163            | Self::Eip1559(r)
164            | Self::Eip7702(r)
165            | Self::Unsigned(r)
166            | Self::Contract(r)
167            | Self::Retry(r)
168            | Self::SubmitRetryable(r)
169            | Self::Internal(r) => {
170                r.status.encode(out);
171                r.cumulative_gas_used.encode(out);
172                r.logs.encode(out);
173            }
174            Self::Deposit(_) => {
175                Eip658Value::Eip658(true).encode(out);
176                (0u64).encode(out);
177                let logs: Vec<Log> = Vec::new();
178                logs.encode(out);
179            }
180        }
181    }
182
183    fn rlp_encoded_fields_length_without_bloom(&self) -> usize {
184        match self {
185            Self::Legacy(r)
186            | Self::Eip2930(r)
187            | Self::Eip1559(r)
188            | Self::Eip7702(r)
189            | Self::Unsigned(r)
190            | Self::Contract(r)
191            | Self::Retry(r)
192            | Self::SubmitRetryable(r)
193            | Self::Internal(r) => {
194                r.status.length() + r.cumulative_gas_used.length() + r.logs.length()
195            }
196            Self::Deposit(_) => {
197                Eip658Value::Eip658(true).length() + (0u64).length() + Vec::<Log>::new().length()
198            }
199        }
200    }
201
202    fn rlp_header_inner_without_bloom(&self) -> alloy_rlp::Header {
203        alloy_rlp::Header {
204            list: true,
205            payload_length: self.rlp_encoded_fields_length_without_bloom(),
206        }
207    }
208
209    fn rlp_decode_inner(
210        buf: &mut &[u8],
211        tx_type: ArbTxType,
212    ) -> alloy_rlp::Result<alloy_consensus::ReceiptWithBloom<ArbReceipt>> {
213        match tx_type {
214            ArbTxType::ArbitrumDepositTx => {
215                let header = alloy_rlp::Header::decode(buf)?;
216                if !header.list {
217                    return Err(alloy_rlp::Error::UnexpectedString);
218                }
219                let remaining = buf.len();
220                let _status: Eip658Value = alloy_rlp::Decodable::decode(buf)?;
221                let _cumu: u64 = alloy_rlp::Decodable::decode(buf)?;
222                let logs_bloom: Bloom = alloy_rlp::Decodable::decode(buf)?;
223                let _logs: Vec<Log> = alloy_rlp::Decodable::decode(buf)?;
224                if buf.len() + header.payload_length != remaining {
225                    return Err(alloy_rlp::Error::UnexpectedLength);
226                }
227                Ok(alloy_consensus::ReceiptWithBloom {
228                    receipt: ArbReceipt::new(ArbReceiptKind::Deposit(ArbDepositReceipt)),
229                    logs_bloom,
230                })
231            }
232            _ => {
233                let alloy_consensus::ReceiptWithBloom {
234                    receipt,
235                    logs_bloom,
236                } = <AlloyReceipt as alloy_consensus::RlpDecodableReceipt>::rlp_decode_with_bloom(
237                    buf,
238                )?;
239                Ok(alloy_consensus::ReceiptWithBloom {
240                    receipt: ArbReceipt::new(ArbReceiptKind::Legacy(receipt)),
241                    logs_bloom,
242                })
243            }
244        }
245    }
246
247    fn rlp_decode_inner_without_bloom(
248        buf: &mut &[u8],
249        tx_type: ArbTxType,
250    ) -> alloy_rlp::Result<ArbReceipt> {
251        let header = alloy_rlp::Header::decode(buf)?;
252        if !header.list {
253            return Err(alloy_rlp::Error::UnexpectedString);
254        }
255        let remaining = buf.len();
256        let status: Eip658Value = alloy_rlp::Decodable::decode(buf)?;
257        let cumulative_gas_used: u64 = alloy_rlp::Decodable::decode(buf)?;
258        let logs: Vec<Log> = alloy_rlp::Decodable::decode(buf)?;
259        if buf.len() + header.payload_length != remaining {
260            return Err(alloy_rlp::Error::UnexpectedLength);
261        }
262        let receipt = AlloyReceipt {
263            status,
264            cumulative_gas_used,
265            logs,
266        };
267        let kind = match tx_type {
268            ArbTxType::ArbitrumDepositTx => ArbReceiptKind::Deposit(ArbDepositReceipt),
269            ArbTxType::ArbitrumUnsignedTx => ArbReceiptKind::Unsigned(receipt),
270            ArbTxType::ArbitrumContractTx => ArbReceiptKind::Contract(receipt),
271            ArbTxType::ArbitrumRetryTx => ArbReceiptKind::Retry(receipt),
272            ArbTxType::ArbitrumSubmitRetryableTx => ArbReceiptKind::SubmitRetryable(receipt),
273            ArbTxType::ArbitrumInternalTx => ArbReceiptKind::Internal(receipt),
274            ArbTxType::ArbitrumLegacyTx => ArbReceiptKind::Legacy(receipt),
275        };
276        Ok(ArbReceipt::new(kind))
277    }
278}
279
280// ---------------------------------------------------------------------------
281// InMemorySize
282// ---------------------------------------------------------------------------
283
284impl InMemorySize for ArbReceipt {
285    fn size(&self) -> usize {
286        core::mem::size_of::<u64>() // gas_used_for_l1
287            + core::mem::size_of::<MultiGas>() // multi_gas_used
288    }
289}
290
291// ---------------------------------------------------------------------------
292// TxReceipt — delegate to kind
293// ---------------------------------------------------------------------------
294
295impl TxReceipt for ArbReceipt {
296    type Log = Log;
297
298    fn status_or_post_state(&self) -> Eip658Value {
299        match &self.kind {
300            ArbReceiptKind::Deposit(_) => Eip658Value::Eip658(true),
301            _ => self.kind.as_receipt().status_or_post_state(),
302        }
303    }
304
305    fn status(&self) -> bool {
306        match &self.kind {
307            ArbReceiptKind::Deposit(_) => true,
308            _ => self.kind.as_receipt().status(),
309        }
310    }
311
312    fn bloom(&self) -> Bloom {
313        match &self.kind {
314            ArbReceiptKind::Deposit(_) => Bloom::ZERO,
315            _ => self.kind.as_receipt().bloom(),
316        }
317    }
318
319    fn cumulative_gas_used(&self) -> u64 {
320        match &self.kind {
321            ArbReceiptKind::Deposit(_) => 0,
322            _ => self.kind.as_receipt().cumulative_gas_used(),
323        }
324    }
325
326    fn logs(&self) -> &[Self::Log] {
327        match &self.kind {
328            ArbReceiptKind::Deposit(_) => &[],
329            _ => self.kind.as_receipt().logs(),
330        }
331    }
332
333    fn into_logs(self) -> Vec<Self::Log> {
334        match self.kind {
335            ArbReceiptKind::Legacy(r)
336            | ArbReceiptKind::Eip2930(r)
337            | ArbReceiptKind::Eip1559(r)
338            | ArbReceiptKind::Eip7702(r)
339            | ArbReceiptKind::Unsigned(r)
340            | ArbReceiptKind::Contract(r)
341            | ArbReceiptKind::Retry(r)
342            | ArbReceiptKind::SubmitRetryable(r)
343            | ArbReceiptKind::Internal(r) => r.logs,
344            ArbReceiptKind::Deposit(_) => Vec::new(),
345        }
346    }
347}
348
349// ---------------------------------------------------------------------------
350// Typed2718 — delegate to kind
351// ---------------------------------------------------------------------------
352
353impl Typed2718 for ArbReceipt {
354    fn is_legacy(&self) -> bool {
355        matches!(self.kind, ArbReceiptKind::Legacy(_))
356    }
357
358    fn ty(&self) -> u8 {
359        match &self.kind {
360            ArbReceiptKind::Legacy(_) => 0x00,
361            ArbReceiptKind::Eip2930(_) => 0x01,
362            ArbReceiptKind::Eip1559(_) => 0x02,
363            ArbReceiptKind::Eip7702(_) => 0x04,
364            ArbReceiptKind::Deposit(_) => 0x64,
365            ArbReceiptKind::Unsigned(_) => 0x65,
366            ArbReceiptKind::Contract(_) => 0x66,
367            ArbReceiptKind::Retry(_) => 0x68,
368            ArbReceiptKind::SubmitRetryable(_) => 0x69,
369            ArbReceiptKind::Internal(_) => 0x6A,
370        }
371    }
372}
373
374// ---------------------------------------------------------------------------
375// Eip2718EncodableReceipt — consensus encoding (no gas_used_for_l1)
376// ---------------------------------------------------------------------------
377
378impl Eip2718EncodableReceipt for ArbReceipt {
379    fn eip2718_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
380        let inner_len = self.kind.rlp_header_inner(bloom).length_with_payload();
381        if !self.is_legacy() {
382            1 + inner_len
383        } else {
384            inner_len
385        }
386    }
387
388    fn eip2718_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn alloy_rlp::bytes::BufMut) {
389        if !self.is_legacy() {
390            out.put_u8(self.ty());
391        }
392        self.kind.rlp_header_inner(bloom).encode(out);
393        self.kind.rlp_encode_fields(bloom, out);
394    }
395}
396
397// ---------------------------------------------------------------------------
398// RlpEncodableReceipt
399// ---------------------------------------------------------------------------
400
401impl alloy_consensus::RlpEncodableReceipt for ArbReceipt {
402    fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
403        let mut len = self.eip2718_encoded_length_with_bloom(bloom);
404        if !self.is_legacy() {
405            len += alloy_rlp::Header {
406                list: false,
407                payload_length: self.eip2718_encoded_length_with_bloom(bloom),
408            }
409            .length();
410        }
411        len
412    }
413
414    fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn alloy_rlp::bytes::BufMut) {
415        if !self.is_legacy() {
416            alloy_rlp::Header {
417                list: false,
418                payload_length: self.eip2718_encoded_length_with_bloom(bloom),
419            }
420            .encode(out);
421        }
422        self.eip2718_encode_with_bloom(bloom, out);
423    }
424}
425
426// ---------------------------------------------------------------------------
427// RlpDecodableReceipt
428// ---------------------------------------------------------------------------
429
430impl alloy_consensus::RlpDecodableReceipt for ArbReceipt {
431    fn rlp_decode_with_bloom(
432        buf: &mut &[u8],
433    ) -> alloy_rlp::Result<alloy_consensus::ReceiptWithBloom<Self>> {
434        let header_buf = &mut &**buf;
435        let header = alloy_rlp::Header::decode(header_buf)?;
436        if header.list {
437            return ArbReceiptKind::rlp_decode_inner(buf, ArbTxType::ArbitrumLegacyTx);
438        }
439        *buf = *header_buf;
440        let remaining = buf.len();
441        let ty = u8::decode(buf)?;
442        let tx_type = ArbTxType::from_u8(ty)
443            .map_err(|_| alloy_rlp::Error::Custom("unexpected arb receipt tx type"))?;
444        let this = ArbReceiptKind::rlp_decode_inner(buf, tx_type)?;
445        if buf.len() + header.payload_length != remaining {
446            return Err(alloy_rlp::Error::UnexpectedLength);
447        }
448        Ok(this)
449    }
450}
451
452// ---------------------------------------------------------------------------
453// Encodable2718 / Decodable2718
454// ---------------------------------------------------------------------------
455
456impl Encodable2718 for ArbReceipt {
457    fn encode_2718_len(&self) -> usize {
458        let type_len = if self.is_legacy() { 0 } else { 1 };
459        type_len
460            + self
461                .kind
462                .rlp_header_inner_without_bloom()
463                .length_with_payload()
464    }
465
466    fn encode_2718(&self, out: &mut dyn alloy_rlp::bytes::BufMut) {
467        if !self.is_legacy() {
468            out.put_u8(self.ty());
469        }
470        self.kind.rlp_header_inner_without_bloom().encode(out);
471        self.kind.rlp_encode_fields_without_bloom(out);
472    }
473}
474
475impl Decodable2718 for ArbReceipt {
476    fn typed_decode(ty: u8, buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Self> {
477        // Standard EVM types use their raw type byte in 2718 encoding but map to
478        // specific ArbReceiptKind variants. ArbTxType only covers Arbitrum-specific
479        // types (0x64+), so handle standard EVM types here first.
480        match ty {
481            0x01 => {
482                return Self::decode_standard_receipt(buf, ArbReceiptKind::Eip2930);
483            }
484            0x02 => {
485                return Self::decode_standard_receipt(buf, ArbReceiptKind::Eip1559);
486            }
487            0x04 => {
488                return Self::decode_standard_receipt(buf, ArbReceiptKind::Eip7702);
489            }
490            _ => {}
491        }
492        let tx_type = ArbTxType::from_u8(ty)
493            .map_err(|_| alloy_eips::eip2718::Eip2718Error::UnexpectedType(ty))?;
494        Ok(ArbReceiptKind::rlp_decode_inner_without_bloom(
495            buf, tx_type,
496        )?)
497    }
498
499    fn fallback_decode(buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Self> {
500        Ok(ArbReceiptKind::rlp_decode_inner_without_bloom(
501            buf,
502            ArbTxType::ArbitrumLegacyTx,
503        )?)
504    }
505}
506
507impl ArbReceipt {
508    /// Decode a standard EVM receipt (EIP-2930, EIP-1559, EIP-7702) from RLP.
509    fn decode_standard_receipt(
510        buf: &mut &[u8],
511        wrap: impl FnOnce(AlloyReceipt) -> ArbReceiptKind,
512    ) -> alloy_eips::eip2718::Eip2718Result<Self> {
513        let header = alloy_rlp::Header::decode(buf)?;
514        if !header.list {
515            return Err(alloy_rlp::Error::UnexpectedString.into());
516        }
517        let remaining = buf.len();
518        let status: Eip658Value = alloy_rlp::Decodable::decode(buf)?;
519        let cumulative_gas_used: u64 = alloy_rlp::Decodable::decode(buf)?;
520        let logs: Vec<Log> = alloy_rlp::Decodable::decode(buf)?;
521        if buf.len() + header.payload_length != remaining {
522            return Err(alloy_rlp::Error::UnexpectedLength.into());
523        }
524        Ok(ArbReceipt::new(wrap(AlloyReceipt {
525            status,
526            cumulative_gas_used,
527            logs,
528        })))
529    }
530}
531
532// ---------------------------------------------------------------------------
533// Encodable / Decodable (RLP) — network encoding
534// ---------------------------------------------------------------------------
535
536impl alloy_rlp::Encodable for ArbReceipt {
537    fn encode(&self, out: &mut dyn alloy_rlp::bytes::BufMut) {
538        self.network_encode(out);
539    }
540
541    fn length(&self) -> usize {
542        self.network_len()
543    }
544}
545
546impl alloy_rlp::Decodable for ArbReceipt {
547    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
548        Ok(Self::network_decode(buf)?)
549    }
550}
551
552// ---------------------------------------------------------------------------
553// serde
554// ---------------------------------------------------------------------------
555
556impl serde::Serialize for ArbReceipt {
557    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
558    where
559        S: serde::Serializer,
560    {
561        use serde::ser::SerializeStruct;
562        let mut state = serializer.serialize_struct("ArbReceipt", 5)?;
563        state.serialize_field("status", &self.status())?;
564        state.serialize_field("cumulative_gas_used", &self.cumulative_gas_used())?;
565        state.serialize_field("ty", &self.ty())?;
566        state.serialize_field("gas_used_for_l1", &self.gas_used_for_l1)?;
567        state.serialize_field("multi_gas_used", &self.multi_gas_used)?;
568        state.end()
569    }
570}
571
572impl<'de> serde::Deserialize<'de> for ArbReceipt {
573    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
574    where
575        D: serde::Deserializer<'de>,
576    {
577        #[derive(serde::Deserialize)]
578        struct Helper {
579            status: bool,
580            cumulative_gas_used: u64,
581            #[serde(default)]
582            ty: u8,
583            #[serde(default)]
584            gas_used_for_l1: u64,
585            #[serde(default)]
586            multi_gas_used: MultiGas,
587        }
588        let helper = Helper::deserialize(deserializer)?;
589        let kind = if helper.ty == 0x64 {
590            ArbReceiptKind::Deposit(ArbDepositReceipt)
591        } else {
592            let receipt = AlloyReceipt {
593                status: Eip658Value::Eip658(helper.status),
594                cumulative_gas_used: helper.cumulative_gas_used,
595                logs: Vec::new(),
596            };
597            ArbReceiptKind::Legacy(receipt)
598        };
599        Ok(ArbReceipt {
600            kind,
601            gas_used_for_l1: helper.gas_used_for_l1,
602            multi_gas_used: helper.multi_gas_used,
603        })
604    }
605}
606
607// ---------------------------------------------------------------------------
608// RlpBincode — required by SerdeBincodeCompat
609// ---------------------------------------------------------------------------
610
611impl reth_primitives_traits::serde_bincode_compat::RlpBincode for ArbReceipt {}
612
613// ---------------------------------------------------------------------------
614// Compact — storage encoding (includes gas_used_for_l1)
615// ---------------------------------------------------------------------------
616
617impl reth_codecs::Compact for ArbReceipt {
618    fn to_compact<B>(&self, buf: &mut B) -> usize
619    where
620        B: bytes::BufMut + AsMut<[u8]>,
621    {
622        // Encode the receipt body via 2718.
623        let mut encoded = Vec::new();
624        self.encode_2718(&mut encoded);
625        let len = encoded.len() as u32;
626        buf.put_u32(len);
627        buf.put_slice(&encoded);
628        // Append gas_used_for_l1 for storage.
629        buf.put_u64(self.gas_used_for_l1);
630        // Append multi_gas_used (8 dimensions + total + refund = 10 u64s).
631        for i in 0..crate::multigas::NUM_RESOURCE_KIND {
632            buf.put_u64(
633                self.multi_gas_used.get(
634                    crate::multigas::ResourceKind::from_u8(i as u8)
635                        .unwrap_or(crate::multigas::ResourceKind::Unknown),
636                ),
637            );
638        }
639        buf.put_u64(self.multi_gas_used.total());
640        buf.put_u64(self.multi_gas_used.refund());
641        0
642    }
643
644    fn from_compact(buf: &[u8], _len: usize) -> (Self, &[u8]) {
645        use bytes::Buf;
646        let mut slice = buf;
647        let receipt_len = slice.get_u32() as usize;
648        let receipt_bytes = &slice[..receipt_len];
649        slice = &slice[receipt_len..];
650
651        let mut rbuf = receipt_bytes;
652        let mut receipt = Self::network_decode(&mut rbuf).unwrap_or_else(|_| {
653            ArbReceipt::new(ArbReceiptKind::Legacy(AlloyReceipt {
654                status: Eip658Value::Eip658(false),
655                cumulative_gas_used: 0,
656                logs: Vec::new(),
657            }))
658        });
659
660        // Read gas_used_for_l1 if present.
661        if slice.len() >= 8 {
662            receipt.gas_used_for_l1 = slice.get_u64();
663        }
664
665        // Read multi_gas_used if present (10 u64s = 80 bytes).
666        if slice.len() >= 80 {
667            let mut gas = [0u64; crate::multigas::NUM_RESOURCE_KIND];
668            for g in &mut gas {
669                *g = slice.get_u64();
670            }
671            let total = slice.get_u64();
672            let refund = slice.get_u64();
673            receipt.multi_gas_used = MultiGas::from_raw(gas, total, refund);
674        }
675
676        (receipt, slice)
677    }
678}
679
680// ---------------------------------------------------------------------------
681// Compress / Decompress — delegates to Compact for database storage
682// ---------------------------------------------------------------------------
683
684impl reth_db_api::table::Compress for ArbReceipt {
685    type Compressed = Vec<u8>;
686
687    fn compress_to_buf<B: bytes::BufMut + AsMut<[u8]>>(&self, buf: &mut B) {
688        let _ = reth_codecs::Compact::to_compact(self, buf);
689    }
690}
691
692impl reth_db_api::table::Decompress for ArbReceipt {
693    fn decompress(value: &[u8]) -> Result<Self, reth_db_api::DatabaseError> {
694        let (obj, _) = reth_codecs::Compact::from_compact(value, value.len());
695        Ok(obj)
696    }
697}