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