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#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct ArbReceipt {
17 pub kind: ArbReceiptKind,
18 pub gas_used_for_l1: u64,
21 pub multi_gas_used: MultiGas,
24}
25
26impl ArbReceipt {
27 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
42pub 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#[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#[derive(Clone, Debug, PartialEq, Eq, Default)]
75pub struct ArbDepositReceipt;
76
77impl 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
280impl InMemorySize for ArbReceipt {
285 fn size(&self) -> usize {
286 core::mem::size_of::<u64>() + core::mem::size_of::<MultiGas>() }
289}
290
291impl 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
349impl 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
374impl 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
397impl 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
426impl 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
452impl 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 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 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
532impl 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
552impl 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
607impl reth_primitives_traits::serde_bincode_compat::RlpBincode for ArbReceipt {}
612
613impl 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 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 buf.put_u64(self.gas_used_for_l1);
630 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 if slice.len() >= 8 {
662 receipt.gas_used_for_l1 = slice.get_u64();
663 }
664
665 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
680impl 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}