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