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