1use core::fmt::Debug;
2
3use alloc::vec::Vec;
4use alloy_consensus::{
5 Eip2718EncodableReceipt, Eip658Value, ReceiptWithBloom, RlpDecodableReceipt,
6 RlpEncodableReceipt, TxReceipt, TxType, Typed2718,
7};
8use alloy_eips::{
9 eip2718::{Eip2718Error, Eip2718Result, Encodable2718, IsTyped2718},
10 Decodable2718,
11};
12use alloy_primitives::{Bloom, Log, B256};
13use alloy_rlp::{BufMut, Decodable, Encodable, Header};
14use reth_primitives_traits::{proofs::ordered_trie_root_with_encoder, InMemorySize};
15
16pub trait TxTy:
18 Debug
19 + Copy
20 + Eq
21 + Send
22 + Sync
23 + InMemorySize
24 + Typed2718
25 + TryFrom<u8, Error = Eip2718Error>
26 + Decodable
27 + 'static
28{
29}
30impl<T> TxTy for T where
31 T: Debug
32 + Copy
33 + Eq
34 + Send
35 + Sync
36 + InMemorySize
37 + Typed2718
38 + TryFrom<u8, Error = Eip2718Error>
39 + Decodable
40 + 'static
41{
42}
43
44#[derive(Clone, Debug, PartialEq, Eq, Default)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
49#[cfg_attr(feature = "reth-codec", reth_codecs::add_arbitrary_tests(compact, rlp))]
50pub struct Receipt<T = TxType> {
51 pub tx_type: T,
53 pub success: bool,
57 pub cumulative_gas_used: u64,
59 pub logs: Vec<Log>,
61}
62
63impl<T: TxTy> Receipt<T> {
64 pub fn rlp_encoded_fields_length(&self, bloom: &Bloom) -> usize {
66 self.success.length() +
67 self.cumulative_gas_used.length() +
68 bloom.length() +
69 self.logs.length()
70 }
71
72 pub fn rlp_encode_fields(&self, bloom: &Bloom, out: &mut dyn BufMut) {
74 self.success.encode(out);
75 self.cumulative_gas_used.encode(out);
76 bloom.encode(out);
77 self.logs.encode(out);
78 }
79
80 pub fn rlp_header_inner(&self, bloom: &Bloom) -> Header {
82 Header { list: true, payload_length: self.rlp_encoded_fields_length(bloom) }
83 }
84
85 pub fn rlp_decode_inner(
88 buf: &mut &[u8],
89 tx_type: T,
90 ) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
91 let header = Header::decode(buf)?;
92 if !header.list {
93 return Err(alloy_rlp::Error::UnexpectedString);
94 }
95
96 let remaining = buf.len();
97
98 let success = Decodable::decode(buf)?;
99 let cumulative_gas_used = Decodable::decode(buf)?;
100 let logs_bloom = Decodable::decode(buf)?;
101 let logs = Decodable::decode(buf)?;
102
103 if buf.len() + header.payload_length != remaining {
104 return Err(alloy_rlp::Error::UnexpectedLength);
105 }
106
107 Ok(ReceiptWithBloom {
108 receipt: Self { cumulative_gas_used, tx_type, success, logs },
109 logs_bloom,
110 })
111 }
112
113 pub fn calculate_receipt_root_no_memo(receipts: &[Self]) -> B256 {
117 ordered_trie_root_with_encoder(receipts, |r, buf| r.with_bloom_ref().encode_2718(buf))
118 }
119
120 pub fn rlp_encoded_fields_length_without_bloom(&self) -> usize {
123 self.success.length() + self.cumulative_gas_used.length() + self.logs.length()
124 }
125
126 pub fn rlp_encode_fields_without_bloom(&self, out: &mut dyn BufMut) {
128 self.success.encode(out);
129 self.cumulative_gas_used.encode(out);
130 self.logs.encode(out);
131 }
132
133 pub fn rlp_header_inner_without_bloom(&self) -> Header {
135 Header { list: true, payload_length: self.rlp_encoded_fields_length_without_bloom() }
136 }
137
138 pub fn rlp_decode_inner_without_bloom(buf: &mut &[u8], tx_type: T) -> alloy_rlp::Result<Self> {
141 let header = Header::decode(buf)?;
142 if !header.list {
143 return Err(alloy_rlp::Error::UnexpectedString);
144 }
145
146 let remaining = buf.len();
147 let success = Decodable::decode(buf)?;
148 let cumulative_gas_used = Decodable::decode(buf)?;
149 let logs = Decodable::decode(buf)?;
150
151 if buf.len() + header.payload_length != remaining {
152 return Err(alloy_rlp::Error::UnexpectedLength);
153 }
154
155 Ok(Self { tx_type, success, cumulative_gas_used, logs })
156 }
157}
158
159impl<T: TxTy> Eip2718EncodableReceipt for Receipt<T> {
160 fn eip2718_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
161 !self.tx_type.is_legacy() as usize + self.rlp_header_inner(bloom).length_with_payload()
162 }
163
164 fn eip2718_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
165 if !self.tx_type.is_legacy() {
166 out.put_u8(self.tx_type.ty());
167 }
168 self.rlp_header_inner(bloom).encode(out);
169 self.rlp_encode_fields(bloom, out);
170 }
171}
172
173impl<T: TxTy> RlpEncodableReceipt for Receipt<T> {
174 fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
175 let mut len = self.eip2718_encoded_length_with_bloom(bloom);
176 if !self.tx_type.is_legacy() {
177 len += Header {
178 list: false,
179 payload_length: self.eip2718_encoded_length_with_bloom(bloom),
180 }
181 .length();
182 }
183
184 len
185 }
186
187 fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
188 if !self.tx_type.is_legacy() {
189 Header { list: false, payload_length: self.eip2718_encoded_length_with_bloom(bloom) }
190 .encode(out);
191 }
192 self.eip2718_encode_with_bloom(bloom, out);
193 }
194}
195
196impl<T: TxTy> RlpDecodableReceipt for Receipt<T> {
197 fn rlp_decode_with_bloom(buf: &mut &[u8]) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
198 let header_buf = &mut &**buf;
199 let header = Header::decode(header_buf)?;
200
201 if header.list {
203 return Self::rlp_decode_inner(buf, T::try_from(0)?)
204 }
205
206 *buf = *header_buf;
208
209 let remaining = buf.len();
210 let tx_type = T::decode(buf)?;
211 let this = Self::rlp_decode_inner(buf, tx_type)?;
212
213 if buf.len() + header.payload_length != remaining {
214 return Err(alloy_rlp::Error::UnexpectedLength);
215 }
216
217 Ok(this)
218 }
219}
220
221impl<T: TxTy> Encodable2718 for Receipt<T> {
222 fn encode_2718_len(&self) -> usize {
223 (!self.tx_type.is_legacy() as usize) +
224 self.rlp_header_inner_without_bloom().length_with_payload()
225 }
226
227 fn encode_2718(&self, out: &mut dyn BufMut) {
229 if !self.tx_type.is_legacy() {
230 out.put_u8(self.tx_type.ty());
231 }
232 self.rlp_header_inner_without_bloom().encode(out);
233 self.rlp_encode_fields_without_bloom(out);
234 }
235}
236
237impl<T: TxTy> Decodable2718 for Receipt<T> {
238 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
239 Ok(Self::rlp_decode_inner_without_bloom(buf, T::try_from(ty)?)?)
240 }
241
242 fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
243 Ok(Self::rlp_decode_inner_without_bloom(buf, T::try_from(0)?)?)
244 }
245}
246
247impl<T: TxTy> Encodable for Receipt<T> {
248 fn encode(&self, out: &mut dyn BufMut) {
249 self.network_encode(out);
250 }
251
252 fn length(&self) -> usize {
253 self.network_len()
254 }
255}
256
257impl<T: TxTy> Decodable for Receipt<T> {
258 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
259 Ok(Self::network_decode(buf)?)
260 }
261}
262
263impl<T: TxTy> TxReceipt for Receipt<T> {
264 type Log = Log;
265
266 fn status_or_post_state(&self) -> Eip658Value {
267 self.success.into()
268 }
269
270 fn status(&self) -> bool {
271 self.success
272 }
273
274 fn bloom(&self) -> Bloom {
275 alloy_primitives::logs_bloom(self.logs())
276 }
277
278 fn cumulative_gas_used(&self) -> u64 {
279 self.cumulative_gas_used
280 }
281
282 fn logs(&self) -> &[Log] {
283 &self.logs
284 }
285
286 fn into_logs(self) -> Vec<Log> {
287 self.logs
288 }
289}
290
291impl<T: TxTy> Typed2718 for Receipt<T> {
292 fn ty(&self) -> u8 {
293 self.tx_type.ty()
294 }
295}
296
297impl<T: TxTy> IsTyped2718 for Receipt<T> {
298 fn is_type(type_id: u8) -> bool {
299 <TxType as IsTyped2718>::is_type(type_id)
300 }
301}
302
303impl<T: TxTy> InMemorySize for Receipt<T> {
304 fn size(&self) -> usize {
305 self.tx_type.size() +
306 core::mem::size_of::<bool>() +
307 core::mem::size_of::<u64>() +
308 self.logs.capacity() * core::mem::size_of::<Log>()
309 }
310}
311
312impl<T> From<alloy_consensus::ReceiptEnvelope<T>> for Receipt<TxType>
313where
314 T: Into<Log>,
315{
316 fn from(value: alloy_consensus::ReceiptEnvelope<T>) -> Self {
317 let value = value.into_primitives_receipt();
318 Self {
319 tx_type: value.tx_type(),
320 success: value.is_success(),
321 cumulative_gas_used: value.cumulative_gas_used(),
322 logs: value.into_logs(),
323 }
324 }
325}
326
327impl<T> From<Receipt<T>> for alloy_consensus::Receipt<Log> {
328 fn from(value: Receipt<T>) -> Self {
329 Self {
330 status: value.success.into(),
331 cumulative_gas_used: value.cumulative_gas_used,
332 logs: value.logs,
333 }
334 }
335}
336
337impl From<Receipt<TxType>> for alloy_consensus::ReceiptEnvelope<Log> {
338 fn from(value: Receipt<TxType>) -> Self {
339 let tx_type = value.tx_type;
340 let receipt = value.into_with_bloom().map_receipt(Into::into);
341 match tx_type {
342 TxType::Legacy => Self::Legacy(receipt),
343 TxType::Eip2930 => Self::Eip2930(receipt),
344 TxType::Eip1559 => Self::Eip1559(receipt),
345 TxType::Eip4844 => Self::Eip4844(receipt),
346 TxType::Eip7702 => Self::Eip7702(receipt),
347 }
348 }
349}
350
351#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
352pub(super) mod serde_bincode_compat {
353 use alloc::{borrow::Cow, vec::Vec};
354 use alloy_consensus::TxType;
355 use alloy_eips::eip2718::Eip2718Error;
356 use alloy_primitives::{Log, U8};
357 use core::fmt::Debug;
358 use serde::{Deserialize, Deserializer, Serialize, Serializer};
359 use serde_with::{DeserializeAs, SerializeAs};
360
361 #[derive(Debug, Serialize, Deserialize)]
378 #[serde(bound(deserialize = "T: TryFrom<u8, Error = Eip2718Error>"))]
379 pub struct Receipt<'a, T = TxType> {
380 #[serde(deserialize_with = "deserde_txtype")]
382 pub tx_type: T,
383 pub success: bool,
387 pub cumulative_gas_used: u64,
389 pub logs: Cow<'a, Vec<Log>>,
391 }
392
393 fn deserde_txtype<'de, D, T>(deserializer: D) -> Result<T, D::Error>
395 where
396 D: Deserializer<'de>,
397 T: TryFrom<u8, Error = Eip2718Error>,
398 {
399 U8::deserialize(deserializer)?.to::<u8>().try_into().map_err(serde::de::Error::custom)
400 }
401
402 impl<'a, T: Copy> From<&'a super::Receipt<T>> for Receipt<'a, T> {
403 fn from(value: &'a super::Receipt<T>) -> Self {
404 Self {
405 tx_type: value.tx_type,
406 success: value.success,
407 cumulative_gas_used: value.cumulative_gas_used,
408 logs: Cow::Borrowed(&value.logs),
409 }
410 }
411 }
412
413 impl<'a, T> From<Receipt<'a, T>> for super::Receipt<T> {
414 fn from(value: Receipt<'a, T>) -> Self {
415 Self {
416 tx_type: value.tx_type,
417 success: value.success,
418 cumulative_gas_used: value.cumulative_gas_used,
419 logs: value.logs.into_owned(),
420 }
421 }
422 }
423
424 impl<T: Copy + Serialize> SerializeAs<super::Receipt<T>> for Receipt<'_, T> {
425 fn serialize_as<S>(source: &super::Receipt<T>, serializer: S) -> Result<S::Ok, S::Error>
426 where
427 S: Serializer,
428 {
429 Receipt::<'_>::from(source).serialize(serializer)
430 }
431 }
432
433 impl<'de, T: TryFrom<u8, Error = Eip2718Error>> DeserializeAs<'de, super::Receipt<T>>
434 for Receipt<'de, T>
435 {
436 fn deserialize_as<D>(deserializer: D) -> Result<super::Receipt<T>, D::Error>
437 where
438 D: Deserializer<'de>,
439 {
440 Receipt::<'_, T>::deserialize(deserializer).map(Into::into)
441 }
442 }
443
444 impl<T> reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat for super::Receipt<T>
445 where
446 T: Copy + Serialize + TryFrom<u8, Error = Eip2718Error> + Debug + 'static,
447 {
448 type BincodeRepr<'a> = Receipt<'a, T>;
449
450 fn as_repr(&self) -> Self::BincodeRepr<'_> {
451 self.into()
452 }
453
454 fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
455 repr.into()
456 }
457 }
458
459 #[cfg(test)]
460 mod tests {
461 use crate::{receipt::serde_bincode_compat, Receipt};
462 use alloy_consensus::TxType;
463 use arbitrary::Arbitrary;
464 use rand::Rng;
465 use serde_with::serde_as;
466
467 #[test]
468 fn test_receipt_bincode_roundtrip() {
469 #[serde_as]
470 #[derive(Debug, PartialEq, Eq)]
471 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
472 struct Data {
473 #[serde_as(as = "serde_bincode_compat::Receipt<'_, TxType>")]
474 receipt: Receipt<TxType>,
475 }
476
477 let mut bytes = [0u8; 1024];
478 rand::rng().fill(bytes.as_mut_slice());
479 let data = Data {
480 receipt: Receipt::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(),
481 };
482 let encoded = bincode::serialize(&data).unwrap();
483 let decoded: Data = bincode::deserialize(&encoded).unwrap();
484 assert_eq!(decoded, data);
485 }
486 }
487}
488
489#[cfg(feature = "reth-codec")]
490mod compact {
491 use super::*;
492 use reth_codecs::{
493 Compact,
494 __private::{modular_bitfield::prelude::*, Buf},
495 };
496
497 impl Receipt {
498 #[doc = "Used bytes by [`ReceiptFlags`]"]
499 pub const fn bitflag_encoded_bytes() -> usize {
500 1u8 as usize
501 }
502 #[doc = "Unused bits for new fields by [`ReceiptFlags`]"]
503 pub const fn bitflag_unused_bits() -> usize {
504 0u8 as usize
505 }
506 }
507
508 #[allow(non_snake_case, unused_parens)]
509 mod flags {
510 use super::*;
511
512 #[doc = "Fieldset that facilitates compacting the parent type. Used bytes: 1 | Unused bits: 0"]
513 #[bitfield]
514 #[derive(Clone, Copy, Debug, Default)]
515 pub struct ReceiptFlags {
516 pub tx_type_len: B2,
517 pub success_len: B1,
518 pub cumulative_gas_used_len: B4,
519 pub __zstd: B1,
520 }
521
522 impl ReceiptFlags {
523 #[doc = r" Deserializes this fieldset and returns it, alongside the original slice in an advanced position."]
524 pub fn from(mut buf: &[u8]) -> (Self, &[u8]) {
525 (Self::from_bytes([buf.get_u8()]), buf)
526 }
527 }
528 }
529
530 pub use flags::ReceiptFlags;
531
532 impl<T: Compact> Compact for Receipt<T> {
533 fn to_compact<B>(&self, buf: &mut B) -> usize
534 where
535 B: reth_codecs::__private::bytes::BufMut + AsMut<[u8]>,
536 {
537 let mut flags = ReceiptFlags::default();
538 let mut total_length = 0;
539 let mut buffer = reth_codecs::__private::bytes::BytesMut::new();
540
541 let tx_type_len = self.tx_type.to_compact(&mut buffer);
542 flags.set_tx_type_len(tx_type_len as u8);
543 let success_len = self.success.to_compact(&mut buffer);
544 flags.set_success_len(success_len as u8);
545 let cumulative_gas_used_len = self.cumulative_gas_used.to_compact(&mut buffer);
546 flags.set_cumulative_gas_used_len(cumulative_gas_used_len as u8);
547 self.logs.to_compact(&mut buffer);
548
549 let zstd = buffer.len() > 7;
550 if zstd {
551 flags.set___zstd(1);
552 }
553
554 let flags = flags.into_bytes();
555 total_length += flags.len() + buffer.len();
556 buf.put_slice(&flags);
557 if zstd {
558 reth_zstd_compressors::RECEIPT_COMPRESSOR.with(|compressor| {
559 let compressed =
560 compressor.borrow_mut().compress(&buffer).expect("Failed to compress.");
561 buf.put(compressed.as_slice());
562 });
563 } else {
564 buf.put(buffer);
565 }
566 total_length
567 }
568
569 fn from_compact(buf: &[u8], _len: usize) -> (Self, &[u8]) {
570 let (flags, mut buf) = ReceiptFlags::from(buf);
571 if flags.__zstd() != 0 {
572 reth_zstd_compressors::RECEIPT_DECOMPRESSOR.with(|decompressor| {
573 let decompressor = &mut decompressor.borrow_mut();
574 let decompressed = decompressor.decompress(buf);
575 let original_buf = buf;
576 let mut buf: &[u8] = decompressed;
577 let (tx_type, new_buf) = T::from_compact(buf, flags.tx_type_len() as usize);
578 buf = new_buf;
579 let (success, new_buf) = bool::from_compact(buf, flags.success_len() as usize);
580 buf = new_buf;
581 let (cumulative_gas_used, new_buf) =
582 u64::from_compact(buf, flags.cumulative_gas_used_len() as usize);
583 buf = new_buf;
584 let (logs, _) = Vec::from_compact(buf, buf.len());
585 (Self { tx_type, success, cumulative_gas_used, logs }, original_buf)
586 })
587 } else {
588 let (tx_type, new_buf) = T::from_compact(buf, flags.tx_type_len() as usize);
589 buf = new_buf;
590 let (success, new_buf) = bool::from_compact(buf, flags.success_len() as usize);
591 buf = new_buf;
592 let (cumulative_gas_used, new_buf) =
593 u64::from_compact(buf, flags.cumulative_gas_used_len() as usize);
594 buf = new_buf;
595 let (logs, new_buf) = Vec::from_compact(buf, buf.len());
596 buf = new_buf;
597 let obj = Self { tx_type, success, cumulative_gas_used, logs };
598 (obj, buf)
599 }
600 }
601 }
602}
603
604#[cfg(feature = "reth-codec")]
605pub use compact::*;
606
607#[cfg(test)]
608mod tests {
609 use super::*;
610 use crate::TransactionSigned;
611 use alloy_eips::eip2718::Encodable2718;
612 use alloy_primitives::{
613 address, b256, bloom, bytes, hex_literal::hex, Address, Bytes, Log, LogData,
614 };
615 use alloy_rlp::Decodable;
616 use reth_codecs::Compact;
617 use reth_primitives_traits::proofs::{
618 calculate_receipt_root, calculate_transaction_root, calculate_withdrawals_root,
619 };
620
621 pub(crate) type Block<T = TransactionSigned> = alloy_consensus::Block<T>;
625
626 #[test]
627 fn test_decode_receipt() {
628 reth_codecs::test_utils::test_decode::<Receipt<TxType>>(&hex!(
629 "c428b52ffd23fc42696156b10200f034792b6a94c3850215c2fef7aea361a0c31b79d9a32652eefc0d4e2e730036061cff7344b6fc6132b50cda0ed810a991ae58ef013150c12b2522533cb3b3a8b19b7786a8b5ff1d3cdc84225e22b02def168c8858df"
630 ));
631 }
632
633 #[test]
635 fn encode_legacy_receipt() {
636 let expected = hex!(
637 "f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"
638 );
639
640 let mut data = Vec::with_capacity(expected.length());
641 let receipt = ReceiptWithBloom {
642 receipt: Receipt {
643 tx_type: TxType::Legacy,
644 cumulative_gas_used: 0x1u64,
645 logs: vec![Log::new_unchecked(
646 address!("0x0000000000000000000000000000000000000011"),
647 vec![
648 b256!("0x000000000000000000000000000000000000000000000000000000000000dead"),
649 b256!("0x000000000000000000000000000000000000000000000000000000000000beef"),
650 ],
651 bytes!("0100ff"),
652 )],
653 success: false,
654 },
655 logs_bloom: [0; 256].into(),
656 };
657
658 receipt.encode(&mut data);
659
660 assert_eq!(receipt.length(), expected.len());
662 assert_eq!(data, expected);
663 }
664
665 #[test]
667 fn decode_legacy_receipt() {
668 let data = hex!(
669 "f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"
670 );
671
672 let expected = ReceiptWithBloom {
674 receipt: Receipt {
675 tx_type: TxType::Legacy,
676 cumulative_gas_used: 0x1u64,
677 logs: vec![Log::new_unchecked(
678 address!("0x0000000000000000000000000000000000000011"),
679 vec![
680 b256!("0x000000000000000000000000000000000000000000000000000000000000dead"),
681 b256!("0x000000000000000000000000000000000000000000000000000000000000beef"),
682 ],
683 bytes!("0100ff"),
684 )],
685 success: false,
686 },
687 logs_bloom: [0; 256].into(),
688 };
689
690 let receipt = ReceiptWithBloom::decode(&mut &data[..]).unwrap();
691 assert_eq!(receipt, expected);
692 }
693
694 #[test]
695 fn gigantic_receipt() {
696 let receipt = Receipt {
697 cumulative_gas_used: 16747627,
698 success: true,
699 tx_type: TxType::Legacy,
700 logs: vec![
701 Log::new_unchecked(
702 address!("0x4bf56695415f725e43c3e04354b604bcfb6dfb6e"),
703 vec![b256!(
704 "0xc69dc3d7ebff79e41f525be431d5cd3cc08f80eaf0f7819054a726eeb7086eb9"
705 )],
706 Bytes::from(vec![1; 0xffffff]),
707 ),
708 Log::new_unchecked(
709 address!("0xfaca325c86bf9c2d5b413cd7b90b209be92229c2"),
710 vec![b256!(
711 "0x8cca58667b1e9ffa004720ac99a3d61a138181963b294d270d91c53d36402ae2"
712 )],
713 Bytes::from(vec![1; 0xffffff]),
714 ),
715 ],
716 };
717
718 let mut data = vec![];
719 receipt.to_compact(&mut data);
720 let (decoded, _) = Receipt::<TxType>::from_compact(&data[..], data.len());
721 assert_eq!(decoded, receipt);
722 }
723
724 #[test]
725 fn test_encode_2718_length() {
726 let receipt = ReceiptWithBloom {
727 receipt: Receipt {
728 tx_type: TxType::Eip1559,
729 success: true,
730 cumulative_gas_used: 21000,
731 logs: vec![],
732 },
733 logs_bloom: Bloom::default(),
734 };
735
736 let encoded = receipt.encoded_2718();
737 assert_eq!(
738 encoded.len(),
739 receipt.encode_2718_len(),
740 "Encoded length should match the actual encoded data length"
741 );
742
743 let legacy_receipt = ReceiptWithBloom {
745 receipt: Receipt {
746 tx_type: TxType::Legacy,
747 success: true,
748 cumulative_gas_used: 21000,
749 logs: vec![],
750 },
751 logs_bloom: Bloom::default(),
752 };
753
754 let legacy_encoded = legacy_receipt.encoded_2718();
755 assert_eq!(
756 legacy_encoded.len(),
757 legacy_receipt.encode_2718_len(),
758 "Encoded length for legacy receipt should match the actual encoded data length"
759 );
760 }
761
762 #[test]
763 fn check_transaction_root() {
764 let data = &hex!(
765 "f90262f901f9a092230ce5476ae868e98c7979cfc165a93f8b6ad1922acf2df62e340916efd49da01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa02307107a867056ca33b5087e77c4174f47625e48fb49f1c70ced34890ddd88f3a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba0c598f69a5674cae9337261b669970e24abc0b46e6d284372a239ec8ccbf20b0ab901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8618203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0"
766 );
767 let block_rlp = &mut data.as_slice();
768 let block: Block = Block::decode(block_rlp).unwrap();
769
770 let tx_root = calculate_transaction_root(&block.body.transactions);
771 assert_eq!(block.transactions_root, tx_root, "Must be the same");
772 }
773
774 #[test]
775 fn check_withdrawals_root() {
776 let data = &hex!(
779 "f90238f90219a0151934ad9b654c50197f37018ee5ee9bb922dec0a1b5e24a6d679cb111cdb107a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0046119afb1ab36aaa8f66088677ed96cd62762f6d3e65642898e189fbe702d51a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff8082079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a048a703da164234812273ea083e4ec3d09d028300cd325b46a6a75402e5a7ab95c0c0d9d8808094c94f5374fce5edbc8e2a8697c15331677e6ebf0b80"
780 );
781 let block: Block = Block::decode(&mut data.as_slice()).unwrap();
782 assert!(block.body.withdrawals.is_some());
783 let withdrawals = block.body.withdrawals.as_ref().unwrap();
784 assert_eq!(withdrawals.len(), 1);
785 let withdrawals_root = calculate_withdrawals_root(withdrawals);
786 assert_eq!(block.withdrawals_root, Some(withdrawals_root));
787
788 let data = &hex!(
791 "f9028cf90219a0151934ad9b654c50197f37018ee5ee9bb922dec0a1b5e24a6d679cb111cdb107a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0ccf7b62d616c2ad7af862d67b9dcd2119a90cebbff8c3cd1e5d7fc99f8755774a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff8082079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a0a95b9a7b58a6b3cb4001eb0be67951c5517141cb0183a255b5cae027a7b10b36c0c0f86cda808094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da028094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da018094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da028094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710"
792 );
793 let block: Block = Block::decode(&mut data.as_slice()).unwrap();
794 assert!(block.body.withdrawals.is_some());
795 let withdrawals = block.body.withdrawals.as_ref().unwrap();
796 assert_eq!(withdrawals.len(), 4);
797 let withdrawals_root = calculate_withdrawals_root(withdrawals);
798 assert_eq!(block.withdrawals_root, Some(withdrawals_root));
799 }
800 #[test]
801 fn check_receipt_root_optimism() {
802 use alloy_consensus::ReceiptWithBloom;
803
804 let logs = vec![Log {
805 address: Address::ZERO,
806 data: LogData::new_unchecked(vec![], Default::default()),
807 }];
808 let bloom = bloom!(
809 "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"
810 );
811 let receipt = ReceiptWithBloom {
812 receipt: Receipt {
813 tx_type: TxType::Eip2930,
814 success: true,
815 cumulative_gas_used: 102068,
816 logs,
817 },
818 logs_bloom: bloom,
819 };
820 let receipt = vec![receipt];
821 let root = calculate_receipt_root(&receipt);
822 assert_eq!(
823 root,
824 b256!("0xfe70ae4a136d98944951b2123859698d59ad251a381abc9960fa81cae3d0d4a0")
825 );
826 }
827}