1use alloc::vec::Vec;
2
3use alloy_consensus::{
4 Eip658Value, Eip2718EncodableReceipt, 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)]
75pub struct ArbDepositReceipt {
76 pub status: bool,
78}
79
80impl Default for ArbDepositReceipt {
81 fn default() -> Self {
82 Self { status: true }
83 }
84}
85
86impl ArbDepositReceipt {
87 pub const fn new(status: bool) -> Self {
88 Self { status }
89 }
90}
91
92impl ArbReceiptKind {
97 pub const fn arb_tx_type(&self) -> ArbTxType {
98 match self {
99 Self::Legacy(_) | Self::Eip2930(_) | Self::Eip1559(_) | Self::Eip7702(_) => {
100 ArbTxType::ArbitrumLegacyTx
101 }
102 Self::Deposit(_) => ArbTxType::ArbitrumDepositTx,
103 Self::Unsigned(_) => ArbTxType::ArbitrumUnsignedTx,
104 Self::Contract(_) => ArbTxType::ArbitrumContractTx,
105 Self::Retry(_) => ArbTxType::ArbitrumRetryTx,
106 Self::SubmitRetryable(_) => ArbTxType::ArbitrumSubmitRetryableTx,
107 Self::Internal(_) => ArbTxType::ArbitrumInternalTx,
108 }
109 }
110
111 pub const fn as_receipt(&self) -> Option<&AlloyReceipt> {
116 match self {
117 Self::Legacy(r)
118 | Self::Eip2930(r)
119 | Self::Eip1559(r)
120 | Self::Eip7702(r)
121 | Self::Unsigned(r)
122 | Self::Contract(r)
123 | Self::Retry(r)
124 | Self::SubmitRetryable(r)
125 | Self::Internal(r) => Some(r),
126 Self::Deposit(_) => None,
127 }
128 }
129
130 fn rlp_encoded_fields_length(&self, bloom: &Bloom) -> usize {
131 match self {
132 Self::Legacy(r)
133 | Self::Eip2930(r)
134 | Self::Eip1559(r)
135 | Self::Eip7702(r)
136 | Self::Unsigned(r)
137 | Self::Contract(r)
138 | Self::Retry(r)
139 | Self::SubmitRetryable(r)
140 | Self::Internal(r) => r.rlp_encoded_fields_length_with_bloom(bloom),
141 Self::Deposit(d) => {
142 Eip658Value::Eip658(d.status).length()
143 + 0u64.length()
144 + bloom.length()
145 + Vec::<Log>::new().length()
146 }
147 }
148 }
149
150 fn rlp_encode_fields(&self, bloom: &Bloom, out: &mut dyn alloy_rlp::bytes::BufMut) {
151 match self {
152 Self::Legacy(r)
153 | Self::Eip2930(r)
154 | Self::Eip1559(r)
155 | Self::Eip7702(r)
156 | Self::Unsigned(r)
157 | Self::Contract(r)
158 | Self::Retry(r)
159 | Self::SubmitRetryable(r)
160 | Self::Internal(r) => r.rlp_encode_fields_with_bloom(bloom, out),
161 Self::Deposit(d) => {
162 Eip658Value::Eip658(d.status).encode(out);
163 (0u64).encode(out);
164 bloom.encode(out);
165 let logs: Vec<Log> = Vec::new();
166 logs.encode(out);
167 }
168 }
169 }
170
171 fn rlp_header_inner(&self, bloom: &Bloom) -> alloy_rlp::Header {
172 alloy_rlp::Header {
173 list: true,
174 payload_length: self.rlp_encoded_fields_length(bloom),
175 }
176 }
177
178 fn rlp_encode_fields_without_bloom(&self, out: &mut dyn alloy_rlp::bytes::BufMut) {
179 match self {
180 Self::Legacy(r)
181 | Self::Eip2930(r)
182 | Self::Eip1559(r)
183 | Self::Eip7702(r)
184 | Self::Unsigned(r)
185 | Self::Contract(r)
186 | Self::Retry(r)
187 | Self::SubmitRetryable(r)
188 | Self::Internal(r) => {
189 r.status.encode(out);
190 r.cumulative_gas_used.encode(out);
191 r.logs.encode(out);
192 }
193 Self::Deposit(d) => {
194 Eip658Value::Eip658(d.status).encode(out);
195 (0u64).encode(out);
196 let logs: Vec<Log> = Vec::new();
197 logs.encode(out);
198 }
199 }
200 }
201
202 fn rlp_encoded_fields_length_without_bloom(&self) -> usize {
203 match self {
204 Self::Legacy(r)
205 | Self::Eip2930(r)
206 | Self::Eip1559(r)
207 | Self::Eip7702(r)
208 | Self::Unsigned(r)
209 | Self::Contract(r)
210 | Self::Retry(r)
211 | Self::SubmitRetryable(r)
212 | Self::Internal(r) => {
213 r.status.length() + r.cumulative_gas_used.length() + r.logs.length()
214 }
215 Self::Deposit(d) => {
216 Eip658Value::Eip658(d.status).length()
217 + (0u64).length()
218 + Vec::<Log>::new().length()
219 }
220 }
221 }
222
223 fn rlp_header_inner_without_bloom(&self) -> alloy_rlp::Header {
224 alloy_rlp::Header {
225 list: true,
226 payload_length: self.rlp_encoded_fields_length_without_bloom(),
227 }
228 }
229
230 fn rlp_decode_inner(
231 buf: &mut &[u8],
232 tx_type: ArbTxType,
233 ) -> alloy_rlp::Result<alloy_consensus::ReceiptWithBloom<ArbReceipt>> {
234 match tx_type {
235 ArbTxType::ArbitrumDepositTx => {
236 let header = alloy_rlp::Header::decode(buf)?;
237 if !header.list {
238 return Err(alloy_rlp::Error::UnexpectedString);
239 }
240 let remaining = buf.len();
241 let status: Eip658Value = alloy_rlp::Decodable::decode(buf)?;
242 let _cumu: u64 = alloy_rlp::Decodable::decode(buf)?;
243 let logs_bloom: Bloom = alloy_rlp::Decodable::decode(buf)?;
244 let _logs: Vec<Log> = alloy_rlp::Decodable::decode(buf)?;
245 if buf.len() + header.payload_length != remaining {
246 return Err(alloy_rlp::Error::UnexpectedLength);
247 }
248 Ok(alloy_consensus::ReceiptWithBloom {
249 receipt: ArbReceipt::new(ArbReceiptKind::Deposit(ArbDepositReceipt::new(
250 status.coerce_status(),
251 ))),
252 logs_bloom,
253 })
254 }
255 _ => {
256 let alloy_consensus::ReceiptWithBloom {
257 receipt,
258 logs_bloom,
259 } = <AlloyReceipt as alloy_consensus::RlpDecodableReceipt>::rlp_decode_with_bloom(
260 buf,
261 )?;
262 Ok(alloy_consensus::ReceiptWithBloom {
263 receipt: ArbReceipt::new(ArbReceiptKind::Legacy(receipt)),
264 logs_bloom,
265 })
266 }
267 }
268 }
269
270 fn rlp_decode_inner_without_bloom(
271 buf: &mut &[u8],
272 tx_type: ArbTxType,
273 ) -> alloy_rlp::Result<ArbReceipt> {
274 let header = alloy_rlp::Header::decode(buf)?;
275 if !header.list {
276 return Err(alloy_rlp::Error::UnexpectedString);
277 }
278 let remaining = buf.len();
279 let status: Eip658Value = alloy_rlp::Decodable::decode(buf)?;
280 let cumulative_gas_used: u64 = alloy_rlp::Decodable::decode(buf)?;
281 let logs: Vec<Log> = alloy_rlp::Decodable::decode(buf)?;
282 if buf.len() + header.payload_length != remaining {
283 return Err(alloy_rlp::Error::UnexpectedLength);
284 }
285 let receipt = AlloyReceipt {
286 status,
287 cumulative_gas_used,
288 logs,
289 };
290 let kind = match tx_type {
291 ArbTxType::ArbitrumDepositTx => {
292 ArbReceiptKind::Deposit(ArbDepositReceipt::new(receipt.status.coerce_status()))
293 }
294 ArbTxType::ArbitrumUnsignedTx => ArbReceiptKind::Unsigned(receipt),
295 ArbTxType::ArbitrumContractTx => ArbReceiptKind::Contract(receipt),
296 ArbTxType::ArbitrumRetryTx => ArbReceiptKind::Retry(receipt),
297 ArbTxType::ArbitrumSubmitRetryableTx => ArbReceiptKind::SubmitRetryable(receipt),
298 ArbTxType::ArbitrumInternalTx => ArbReceiptKind::Internal(receipt),
299 ArbTxType::ArbitrumLegacyTx => ArbReceiptKind::Legacy(receipt),
300 };
301 Ok(ArbReceipt::new(kind))
302 }
303}
304
305impl InMemorySize for ArbReceipt {
310 fn size(&self) -> usize {
311 core::mem::size_of::<u64>() + core::mem::size_of::<MultiGas>() }
314}
315
316impl TxReceipt for ArbReceipt {
321 type Log = Log;
322
323 fn status_or_post_state(&self) -> Eip658Value {
324 match self.kind.as_receipt() {
325 Some(r) => r.status_or_post_state(),
326 None => match &self.kind {
327 ArbReceiptKind::Deposit(d) => Eip658Value::Eip658(d.status),
328 _ => Eip658Value::Eip658(true),
329 },
330 }
331 }
332
333 fn status(&self) -> bool {
334 match self.kind.as_receipt() {
335 Some(r) => r.status(),
336 None => match &self.kind {
337 ArbReceiptKind::Deposit(d) => d.status,
338 _ => true,
339 },
340 }
341 }
342
343 fn bloom(&self) -> Bloom {
344 match self.kind.as_receipt() {
345 Some(r) => r.bloom(),
346 None => Bloom::ZERO,
347 }
348 }
349
350 fn cumulative_gas_used(&self) -> u64 {
351 match self.kind.as_receipt() {
352 Some(r) => r.cumulative_gas_used(),
353 None => 0,
354 }
355 }
356
357 fn logs(&self) -> &[Self::Log] {
358 match self.kind.as_receipt() {
359 Some(r) => r.logs(),
360 None => &[],
361 }
362 }
363
364 fn into_logs(self) -> Vec<Self::Log> {
365 match self.kind {
366 ArbReceiptKind::Legacy(r)
367 | ArbReceiptKind::Eip2930(r)
368 | ArbReceiptKind::Eip1559(r)
369 | ArbReceiptKind::Eip7702(r)
370 | ArbReceiptKind::Unsigned(r)
371 | ArbReceiptKind::Contract(r)
372 | ArbReceiptKind::Retry(r)
373 | ArbReceiptKind::SubmitRetryable(r)
374 | ArbReceiptKind::Internal(r) => r.logs,
375 ArbReceiptKind::Deposit(_) => Vec::new(),
376 }
377 }
378}
379
380impl Typed2718 for ArbReceipt {
385 fn is_legacy(&self) -> bool {
386 matches!(self.kind, ArbReceiptKind::Legacy(_))
387 }
388
389 fn ty(&self) -> u8 {
390 match &self.kind {
391 ArbReceiptKind::Legacy(_) => 0x00,
392 ArbReceiptKind::Eip2930(_) => 0x01,
393 ArbReceiptKind::Eip1559(_) => 0x02,
394 ArbReceiptKind::Eip7702(_) => 0x04,
395 ArbReceiptKind::Deposit(_) => 0x64,
396 ArbReceiptKind::Unsigned(_) => 0x65,
397 ArbReceiptKind::Contract(_) => 0x66,
398 ArbReceiptKind::Retry(_) => 0x68,
399 ArbReceiptKind::SubmitRetryable(_) => 0x69,
400 ArbReceiptKind::Internal(_) => 0x6A,
401 }
402 }
403}
404
405impl Eip2718EncodableReceipt for ArbReceipt {
410 fn eip2718_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
411 let inner_len = self.kind.rlp_header_inner(bloom).length_with_payload();
412 if !self.is_legacy() {
413 1 + inner_len
414 } else {
415 inner_len
416 }
417 }
418
419 fn eip2718_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn alloy_rlp::bytes::BufMut) {
420 if !self.is_legacy() {
421 out.put_u8(self.ty());
422 }
423 self.kind.rlp_header_inner(bloom).encode(out);
424 self.kind.rlp_encode_fields(bloom, out);
425 }
426}
427
428impl alloy_consensus::RlpEncodableReceipt for ArbReceipt {
433 fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
434 let mut len = self.eip2718_encoded_length_with_bloom(bloom);
435 if !self.is_legacy() {
436 len += alloy_rlp::Header {
437 list: false,
438 payload_length: self.eip2718_encoded_length_with_bloom(bloom),
439 }
440 .length();
441 }
442 len
443 }
444
445 fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn alloy_rlp::bytes::BufMut) {
446 if !self.is_legacy() {
447 alloy_rlp::Header {
448 list: false,
449 payload_length: self.eip2718_encoded_length_with_bloom(bloom),
450 }
451 .encode(out);
452 }
453 self.eip2718_encode_with_bloom(bloom, out);
454 }
455}
456
457impl alloy_consensus::RlpDecodableReceipt for ArbReceipt {
462 fn rlp_decode_with_bloom(
463 buf: &mut &[u8],
464 ) -> alloy_rlp::Result<alloy_consensus::ReceiptWithBloom<Self>> {
465 let header_buf = &mut &**buf;
466 let header = alloy_rlp::Header::decode(header_buf)?;
467 if header.list {
468 return ArbReceiptKind::rlp_decode_inner(buf, ArbTxType::ArbitrumLegacyTx);
469 }
470 *buf = *header_buf;
471 let remaining = buf.len();
472 let ty = u8::decode(buf)?;
473 let tx_type = ArbTxType::from_u8(ty)
474 .map_err(|_| alloy_rlp::Error::Custom("unexpected arb receipt tx type"))?;
475 let this = ArbReceiptKind::rlp_decode_inner(buf, tx_type)?;
476 if buf.len() + header.payload_length != remaining {
477 return Err(alloy_rlp::Error::UnexpectedLength);
478 }
479 Ok(this)
480 }
481}
482
483impl Encodable2718 for ArbReceipt {
488 fn encode_2718_len(&self) -> usize {
489 let type_len = if self.is_legacy() { 0 } else { 1 };
490 type_len
491 + self
492 .kind
493 .rlp_header_inner_without_bloom()
494 .length_with_payload()
495 }
496
497 fn encode_2718(&self, out: &mut dyn alloy_rlp::bytes::BufMut) {
498 if !self.is_legacy() {
499 out.put_u8(self.ty());
500 }
501 self.kind.rlp_header_inner_without_bloom().encode(out);
502 self.kind.rlp_encode_fields_without_bloom(out);
503 }
504}
505
506impl Decodable2718 for ArbReceipt {
507 fn typed_decode(ty: u8, buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Self> {
508 match ty {
512 0x01 => {
513 return Self::decode_standard_receipt(buf, ArbReceiptKind::Eip2930);
514 }
515 0x02 => {
516 return Self::decode_standard_receipt(buf, ArbReceiptKind::Eip1559);
517 }
518 0x04 => {
519 return Self::decode_standard_receipt(buf, ArbReceiptKind::Eip7702);
520 }
521 _ => {}
522 }
523 let tx_type = ArbTxType::from_u8(ty)
524 .map_err(|_| alloy_eips::eip2718::Eip2718Error::UnexpectedType(ty))?;
525 Ok(ArbReceiptKind::rlp_decode_inner_without_bloom(
526 buf, tx_type,
527 )?)
528 }
529
530 fn fallback_decode(buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Self> {
531 Ok(ArbReceiptKind::rlp_decode_inner_without_bloom(
532 buf,
533 ArbTxType::ArbitrumLegacyTx,
534 )?)
535 }
536}
537
538impl ArbReceipt {
539 fn decode_standard_receipt(
541 buf: &mut &[u8],
542 wrap: impl FnOnce(AlloyReceipt) -> ArbReceiptKind,
543 ) -> alloy_eips::eip2718::Eip2718Result<Self> {
544 let header = alloy_rlp::Header::decode(buf)?;
545 if !header.list {
546 return Err(alloy_rlp::Error::UnexpectedString.into());
547 }
548 let remaining = buf.len();
549 let status: Eip658Value = alloy_rlp::Decodable::decode(buf)?;
550 let cumulative_gas_used: u64 = alloy_rlp::Decodable::decode(buf)?;
551 let logs: Vec<Log> = alloy_rlp::Decodable::decode(buf)?;
552 if buf.len() + header.payload_length != remaining {
553 return Err(alloy_rlp::Error::UnexpectedLength.into());
554 }
555 Ok(ArbReceipt::new(wrap(AlloyReceipt {
556 status,
557 cumulative_gas_used,
558 logs,
559 })))
560 }
561}
562
563impl alloy_rlp::Encodable for ArbReceipt {
568 fn encode(&self, out: &mut dyn alloy_rlp::bytes::BufMut) {
569 self.network_encode(out);
570 }
571
572 fn length(&self) -> usize {
573 self.network_len()
574 }
575}
576
577impl alloy_rlp::Decodable for ArbReceipt {
578 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
579 Ok(Self::network_decode(buf)?)
580 }
581}
582
583impl serde::Serialize for ArbReceipt {
588 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
589 where
590 S: serde::Serializer,
591 {
592 use serde::ser::SerializeStruct;
593 let mut state = serializer.serialize_struct("ArbReceipt", 5)?;
594 state.serialize_field("status", &self.status())?;
595 state.serialize_field("cumulative_gas_used", &self.cumulative_gas_used())?;
596 state.serialize_field("ty", &self.ty())?;
597 state.serialize_field("gas_used_for_l1", &self.gas_used_for_l1)?;
598 state.serialize_field("multi_gas_used", &self.multi_gas_used)?;
599 state.end()
600 }
601}
602
603impl<'de> serde::Deserialize<'de> for ArbReceipt {
604 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
605 where
606 D: serde::Deserializer<'de>,
607 {
608 #[derive(serde::Deserialize)]
609 struct Helper {
610 status: bool,
611 cumulative_gas_used: u64,
612 #[serde(default)]
613 ty: u8,
614 #[serde(default)]
615 gas_used_for_l1: u64,
616 #[serde(default)]
617 multi_gas_used: MultiGas,
618 }
619 let helper = Helper::deserialize(deserializer)?;
620 let kind = if helper.ty == 0x64 {
621 ArbReceiptKind::Deposit(ArbDepositReceipt::new(helper.status))
622 } else {
623 let receipt = AlloyReceipt {
624 status: Eip658Value::Eip658(helper.status),
625 cumulative_gas_used: helper.cumulative_gas_used,
626 logs: Vec::new(),
627 };
628 ArbReceiptKind::Legacy(receipt)
629 };
630 Ok(ArbReceipt {
631 kind,
632 gas_used_for_l1: helper.gas_used_for_l1,
633 multi_gas_used: helper.multi_gas_used,
634 })
635 }
636}
637
638impl reth_primitives_traits::serde_bincode_compat::RlpBincode for ArbReceipt {}
643
644impl reth_codecs::Compact for ArbReceipt {
649 fn to_compact<B>(&self, buf: &mut B) -> usize
650 where
651 B: bytes::BufMut + AsMut<[u8]>,
652 {
653 let mut encoded = Vec::new();
655 self.encode_2718(&mut encoded);
656 let len = encoded.len() as u32;
657 buf.put_u32(len);
658 buf.put_slice(&encoded);
659 buf.put_u64(self.gas_used_for_l1);
661 for i in 0..crate::multigas::NUM_RESOURCE_KIND {
663 buf.put_u64(
664 self.multi_gas_used.get(
665 crate::multigas::ResourceKind::from_u8(i as u8)
666 .unwrap_or(crate::multigas::ResourceKind::Unknown),
667 ),
668 );
669 }
670 buf.put_u64(self.multi_gas_used.total());
671 buf.put_u64(self.multi_gas_used.refund());
672 0
673 }
674
675 fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
676 use bytes::Buf;
677 let mut slice = buf;
678 let receipt_len = slice.get_u32() as usize;
679 let receipt_bytes = &slice[..receipt_len];
680 slice = &slice[receipt_len..];
681
682 let mut rbuf = receipt_bytes;
683 let mut receipt = Self::network_decode(&mut rbuf).unwrap_or_else(|_| {
684 ArbReceipt::new(ArbReceiptKind::Legacy(AlloyReceipt {
685 status: Eip658Value::Eip658(false),
686 cumulative_gas_used: 0,
687 logs: Vec::new(),
688 }))
689 });
690
691 let mut tail = len.saturating_sub(4 + receipt_len).min(slice.len());
694
695 if tail >= 8 {
696 receipt.gas_used_for_l1 = slice.get_u64();
697 tail -= 8;
698 }
699
700 if tail >= 16 {
705 let kinds_on_disk = (tail - 16) / 8;
706 let store = kinds_on_disk.min(crate::multigas::NUM_RESOURCE_KIND);
707 let mut gas = [0u64; crate::multigas::NUM_RESOURCE_KIND];
708 for slot in gas.iter_mut().take(store) {
709 *slot = slice.get_u64();
710 }
711 for _ in store..kinds_on_disk {
714 slice.advance(8);
715 }
716 let total = slice.get_u64();
717 let refund = slice.get_u64();
718 receipt.multi_gas_used = MultiGas::from_raw(gas, total, refund);
719 }
720
721 (receipt, slice)
722 }
723}
724
725impl reth_db_api::table::Compress for ArbReceipt {
730 type Compressed = Vec<u8>;
731
732 fn compress_to_buf<B: bytes::BufMut + AsMut<[u8]>>(&self, buf: &mut B) {
733 let _ = reth_codecs::Compact::to_compact(self, buf);
734 }
735}
736
737impl reth_db_api::table::Decompress for ArbReceipt {
738 fn decompress(value: &[u8]) -> Result<Self, reth_db_api::DatabaseError> {
739 let (obj, _) = reth_codecs::Compact::from_compact(value, value.len());
740 Ok(obj)
741 }
742}
743
744#[cfg(test)]
745mod tests {
746 use super::*;
747
748 fn alloy_receipt() -> AlloyReceipt {
749 AlloyReceipt {
750 status: Eip658Value::Eip658(true),
751 cumulative_gas_used: 21_000,
752 logs: Vec::new(),
753 }
754 }
755
756 #[test]
757 fn as_receipt_returns_none_for_deposit() {
758 let kind = ArbReceiptKind::Deposit(ArbDepositReceipt::default());
759 assert!(kind.as_receipt().is_none());
760 }
761
762 #[test]
763 fn as_receipt_returns_some_for_non_deposit() {
764 let kind = ArbReceiptKind::Legacy(alloy_receipt());
765 assert!(kind.as_receipt().is_some());
766 }
767
768 #[test]
769 fn tx_receipt_trait_methods_handle_deposit() {
770 let receipt = ArbReceipt::new(ArbReceiptKind::Deposit(ArbDepositReceipt::default()));
771 assert!(receipt.status());
772 assert_eq!(receipt.status_or_post_state(), Eip658Value::Eip658(true));
773 assert_eq!(receipt.bloom(), Bloom::ZERO);
774 assert_eq!(receipt.cumulative_gas_used(), 0);
775 assert!(receipt.logs().is_empty());
776 }
777
778 #[test]
779 fn tx_receipt_trait_methods_delegate_for_non_deposit() {
780 let receipt = ArbReceipt::new(ArbReceiptKind::Legacy(alloy_receipt()));
781 assert!(receipt.status());
782 assert_eq!(receipt.cumulative_gas_used(), 21_000);
783 }
784
785 const NUM: usize = crate::multigas::NUM_RESOURCE_KIND;
786
787 fn compact_with_kinds(receipt: &ArbReceipt, gas: &[u64], total: u64, refund: u64) -> Vec<u8> {
791 use bytes::BufMut;
792 let mut body = Vec::new();
793 receipt.encode_2718(&mut body);
794 let mut buf = Vec::new();
795 buf.put_u32(body.len() as u32);
796 buf.put_slice(&body);
797 buf.put_u64(receipt.gas_used_for_l1);
798 for &g in gas {
799 buf.put_u64(g);
800 }
801 buf.put_u64(total);
802 buf.put_u64(refund);
803 buf
804 }
805
806 fn decode(buf: &[u8]) -> ArbReceipt {
807 let (receipt, rest) = <ArbReceipt as reth_codecs::Compact>::from_compact(buf, buf.len());
808 assert!(rest.is_empty(), "{} trailing bytes left", rest.len());
809 receipt
810 }
811
812 #[test]
813 fn from_compact_round_trips_current_kinds() {
814 let mut receipt = ArbReceipt::new(ArbReceiptKind::Eip1559(alloy_receipt()));
815 receipt.gas_used_for_l1 = 4242;
816 let gas: [u64; NUM] = std::array::from_fn(|i| (i as u64 + 1) * 10);
817 receipt.multi_gas_used = MultiGas::from_raw(gas, 999, 7);
818 let mut buf = Vec::new();
819 reth_codecs::Compact::to_compact(&receipt, &mut buf);
820 let decoded = decode(&buf);
821 assert_eq!(decoded.gas_used_for_l1, 4242);
822 assert_eq!(decoded.multi_gas_used, receipt.multi_gas_used);
823 }
824
825 #[test]
826 fn from_compact_pads_receipt_with_fewer_kinds() {
827 let mut receipt = ArbReceipt::new(ArbReceiptKind::Eip1559(alloy_receipt()));
830 receipt.gas_used_for_l1 = 11;
831 let gas: Vec<u64> = (1..=8).map(|n| n * 100).collect();
832 let decoded = decode(&compact_with_kinds(&receipt, &gas, 800, 5));
833 let mut expected = [0u64; NUM];
834 expected[..gas.len()].copy_from_slice(&gas);
835 assert_eq!(decoded.gas_used_for_l1, 11);
836 assert_eq!(decoded.multi_gas_used, MultiGas::from_raw(expected, 800, 5));
837 }
838
839 #[test]
842 fn canonical_block1_retry_tx_hash() {
843 use alloy_primitives::{Bytes, U256, address, b256, hex, keccak256};
844 use arb_alloy_consensus::tx::ArbRetryTx;
845 let tx = ArbRetryTx {
846 chain_id: U256::from(421614u64),
847 nonce: 0,
848 from: address!("b8787d8f23e176a5d32135d746b69886e03313be"),
849 gas_fee_cap: U256::from(0x5f5e100u64),
850 gas: 100_000,
851 to: Some(address!("3fab184622dc19b6109349b94811493bf2a45362")),
852 value: U256::from(0x2386f26fc10000u64),
853 data: Bytes::new(),
854 ticket_id: b256!("13cb79b086a427f3db7ebe6ec2bb90a806a3b0368ecee6020144f352e37dbdf6"),
855 refund_to: address!("11155ca9bbf7be58e27f3309e629c847996b43c8"),
856 max_refund: U256::from(0xb0e85efeab8u64),
857 submission_fee_refund: U256::from(0x1f6377d4ab8u64),
858 };
859 let mut enc = Vec::new();
860 enc.push(ArbTxType::ArbitrumRetryTx.as_u8());
861 alloy_rlp::Encodable::encode(&tx, &mut enc);
862 assert_eq!(
863 keccak256(&enc),
864 b256!("873c5ee3092c40336006808e249293bf5f4cb3235077a74cac9cafa7cf73cb8b"),
865 "retry-tx hash mismatch; our preimage = 0x{}",
866 hex::encode(&enc)
867 );
868 }
869
870 #[test]
873 fn canonical_block1_submit_retryable_hash() {
874 use alloy_primitives::{Bytes, U256, address, b256, hex, keccak256};
875 use arb_alloy_consensus::tx::ArbSubmitRetryableTx;
876 let tx = ArbSubmitRetryableTx {
877 chain_id: U256::from(421614u64),
878 request_id: b256!("0000000000000000000000000000000000000000000000000000000000000001"),
879 from: address!("b8787d8f23e176a5d32135d746b69886e03313be"),
880 l1_base_fee: U256::from(0x5bd57bd9u64),
881 deposit_value: U256::from(0x23e3dbb7b88ab8u64),
882 gas_fee_cap: U256::from(0x3b9aca00u64),
883 gas: 100_000,
884 retry_to: Some(address!("3fab184622dc19b6109349b94811493bf2a45362")),
885 retry_value: U256::from(0x2386f26fc10000u64),
886 beneficiary: address!("11155ca9bbf7be58e27f3309e629c847996b43c8"),
887 max_submission_fee: U256::from(0x1f6377d4ab8u64),
888 fee_refund_addr: address!("11155ca9bbf7be58e27f3309e629c847996b43c8"),
889 retry_data: Bytes::new(),
890 };
891 let mut enc = Vec::new();
892 enc.push(ArbTxType::ArbitrumSubmitRetryableTx.as_u8());
893 alloy_rlp::Encodable::encode(&tx, &mut enc);
894 assert_eq!(
895 keccak256(&enc),
896 b256!("13cb79b086a427f3db7ebe6ec2bb90a806a3b0368ecee6020144f352e37dbdf6"),
897 "submit-retryable hash mismatch; our preimage = 0x{}",
898 hex::encode(&enc)
899 );
900 }
901
902 #[test]
903 fn from_compact_truncates_receipt_with_more_kinds() {
904 let mut receipt = ArbReceipt::new(ArbReceiptKind::Eip1559(alloy_receipt()));
907 receipt.gas_used_for_l1 = 22;
908 let gas: Vec<u64> = (1..=(NUM as u64 + 1)).map(|n| n * 100).collect();
909 let decoded = decode(&compact_with_kinds(&receipt, &gas, 1000, 9));
910 let mut expected = [0u64; NUM];
911 expected.copy_from_slice(&gas[..NUM]);
912 assert_eq!(
913 decoded.multi_gas_used,
914 MultiGas::from_raw(expected, 1000, 9)
915 );
916 }
917}