1use alloc::vec::Vec;
2use alloy_consensus::{
3 Eip2718EncodableReceipt, Eip658Value, ReceiptWithBloom, RlpDecodableReceipt,
4 RlpEncodableReceipt, TxReceipt, TxType, Typed2718,
5};
6use alloy_eips::{
7 eip2718::{Eip2718Result, Encodable2718, IsTyped2718},
8 Decodable2718,
9};
10use alloy_primitives::{Bloom, Log, B256};
11use alloy_rlp::{BufMut, Decodable, Encodable, Header};
12use reth_primitives_traits::{proofs::ordered_trie_root_with_encoder, InMemorySize};
13
14#[derive(Clone, Debug, PartialEq, Eq, Default)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
19#[cfg_attr(feature = "reth-codec", derive(reth_codecs::CompactZstd))]
20#[cfg_attr(feature = "reth-codec", reth_codecs::add_arbitrary_tests(compact, rlp))]
21#[cfg_attr(feature = "reth-codec", reth_zstd(
22 compressor = reth_zstd_compressors::RECEIPT_COMPRESSOR,
23 decompressor = reth_zstd_compressors::RECEIPT_DECOMPRESSOR
24))]
25pub struct Receipt {
26 pub tx_type: TxType,
28 pub success: bool,
32 pub cumulative_gas_used: u64,
34 pub logs: Vec<Log>,
36}
37
38impl Receipt {
39 pub fn rlp_encoded_fields_length(&self, bloom: &Bloom) -> usize {
41 self.success.length() +
42 self.cumulative_gas_used.length() +
43 bloom.length() +
44 self.logs.length()
45 }
46
47 pub fn rlp_encode_fields(&self, bloom: &Bloom, out: &mut dyn BufMut) {
49 self.success.encode(out);
50 self.cumulative_gas_used.encode(out);
51 bloom.encode(out);
52 self.logs.encode(out);
53 }
54
55 pub fn rlp_header_inner(&self, bloom: &Bloom) -> Header {
57 Header { list: true, payload_length: self.rlp_encoded_fields_length(bloom) }
58 }
59
60 pub fn rlp_decode_inner(
63 buf: &mut &[u8],
64 tx_type: TxType,
65 ) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
66 let header = Header::decode(buf)?;
67 if !header.list {
68 return Err(alloy_rlp::Error::UnexpectedString);
69 }
70
71 let remaining = buf.len();
72
73 let success = Decodable::decode(buf)?;
74 let cumulative_gas_used = Decodable::decode(buf)?;
75 let logs_bloom = Decodable::decode(buf)?;
76 let logs = Decodable::decode(buf)?;
77
78 if buf.len() + header.payload_length != remaining {
79 return Err(alloy_rlp::Error::UnexpectedLength);
80 }
81
82 Ok(ReceiptWithBloom {
83 receipt: Self { cumulative_gas_used, tx_type, success, logs },
84 logs_bloom,
85 })
86 }
87
88 pub fn calculate_receipt_root_no_memo(receipts: &[Self]) -> B256 {
92 ordered_trie_root_with_encoder(receipts, |r, buf| r.with_bloom_ref().encode_2718(buf))
93 }
94
95 pub fn rlp_encoded_fields_length_without_bloom(&self) -> usize {
98 self.success.length() + self.cumulative_gas_used.length() + self.logs.length()
99 }
100
101 pub fn rlp_encode_fields_without_bloom(&self, out: &mut dyn BufMut) {
103 self.success.encode(out);
104 self.cumulative_gas_used.encode(out);
105 self.logs.encode(out);
106 }
107
108 pub fn rlp_header_inner_without_bloom(&self) -> Header {
110 Header { list: true, payload_length: self.rlp_encoded_fields_length_without_bloom() }
111 }
112
113 pub fn rlp_decode_inner_without_bloom(
116 buf: &mut &[u8],
117 tx_type: TxType,
118 ) -> alloy_rlp::Result<Self> {
119 let header = Header::decode(buf)?;
120 if !header.list {
121 return Err(alloy_rlp::Error::UnexpectedString);
122 }
123
124 let remaining = buf.len();
125 let success = Decodable::decode(buf)?;
126 let cumulative_gas_used = Decodable::decode(buf)?;
127 let logs = Decodable::decode(buf)?;
128
129 if buf.len() + header.payload_length != remaining {
130 return Err(alloy_rlp::Error::UnexpectedLength);
131 }
132
133 Ok(Self { tx_type, success, cumulative_gas_used, logs })
134 }
135}
136
137impl Eip2718EncodableReceipt for Receipt {
138 fn eip2718_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
139 !self.tx_type.is_legacy() as usize + self.rlp_header_inner(bloom).length_with_payload()
140 }
141
142 fn eip2718_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
143 if !self.tx_type.is_legacy() {
144 out.put_u8(self.tx_type as u8);
145 }
146 self.rlp_header_inner(bloom).encode(out);
147 self.rlp_encode_fields(bloom, out);
148 }
149}
150
151impl RlpEncodableReceipt for Receipt {
152 fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
153 let mut len = self.eip2718_encoded_length_with_bloom(bloom);
154 if !self.tx_type.is_legacy() {
155 len += Header {
156 list: false,
157 payload_length: self.eip2718_encoded_length_with_bloom(bloom),
158 }
159 .length();
160 }
161
162 len
163 }
164
165 fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
166 if !self.tx_type.is_legacy() {
167 Header { list: false, payload_length: self.eip2718_encoded_length_with_bloom(bloom) }
168 .encode(out);
169 }
170 self.eip2718_encode_with_bloom(bloom, out);
171 }
172}
173
174impl RlpDecodableReceipt for Receipt {
175 fn rlp_decode_with_bloom(buf: &mut &[u8]) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
176 let header_buf = &mut &**buf;
177 let header = Header::decode(header_buf)?;
178
179 if header.list {
181 return Self::rlp_decode_inner(buf, TxType::Legacy)
182 }
183
184 *buf = *header_buf;
186
187 let remaining = buf.len();
188 let tx_type = TxType::decode(buf)?;
189 let this = Self::rlp_decode_inner(buf, tx_type)?;
190
191 if buf.len() + header.payload_length != remaining {
192 return Err(alloy_rlp::Error::UnexpectedLength);
193 }
194
195 Ok(this)
196 }
197}
198
199impl Encodable2718 for Receipt {
200 fn encode_2718_len(&self) -> usize {
201 (!self.tx_type.is_legacy() as usize) +
202 self.rlp_header_inner_without_bloom().length_with_payload()
203 }
204
205 fn encode_2718(&self, out: &mut dyn BufMut) {
207 if !self.tx_type.is_legacy() {
208 out.put_u8(self.tx_type as u8);
209 }
210 self.rlp_header_inner_without_bloom().encode(out);
211 self.rlp_encode_fields_without_bloom(out);
212 }
213}
214
215impl Decodable2718 for Receipt {
216 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
217 Ok(Self::rlp_decode_inner_without_bloom(buf, TxType::try_from(ty)?)?)
218 }
219
220 fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
221 Ok(Self::rlp_decode_inner_without_bloom(buf, TxType::Legacy)?)
222 }
223}
224
225impl Encodable for Receipt {
226 fn encode(&self, out: &mut dyn BufMut) {
227 self.network_encode(out);
228 }
229
230 fn length(&self) -> usize {
231 self.network_len()
232 }
233}
234
235impl Decodable for Receipt {
236 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
237 Ok(Self::network_decode(buf)?)
238 }
239}
240
241impl TxReceipt for Receipt {
242 type Log = Log;
243
244 fn status_or_post_state(&self) -> Eip658Value {
245 self.success.into()
246 }
247
248 fn status(&self) -> bool {
249 self.success
250 }
251
252 fn bloom(&self) -> Bloom {
253 alloy_primitives::logs_bloom(self.logs())
254 }
255
256 fn cumulative_gas_used(&self) -> u64 {
257 self.cumulative_gas_used
258 }
259
260 fn logs(&self) -> &[Log] {
261 &self.logs
262 }
263}
264
265impl Typed2718 for Receipt {
266 fn ty(&self) -> u8 {
267 self.tx_type as u8
268 }
269}
270
271impl IsTyped2718 for Receipt {
272 fn is_type(type_id: u8) -> bool {
273 <TxType as IsTyped2718>::is_type(type_id)
274 }
275}
276
277impl InMemorySize for Receipt {
278 fn size(&self) -> usize {
279 self.tx_type.size() +
280 core::mem::size_of::<bool>() +
281 core::mem::size_of::<u64>() +
282 self.logs.capacity() * core::mem::size_of::<Log>()
283 }
284}
285
286impl reth_primitives_traits::Receipt for Receipt {}
287
288#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
289pub(super) mod serde_bincode_compat {
290 use alloc::{borrow::Cow, vec::Vec};
291 use alloy_consensus::TxType;
292 use alloy_primitives::{Log, U8};
293 use serde::{Deserialize, Deserializer, Serialize, Serializer};
294 use serde_with::{DeserializeAs, SerializeAs};
295
296 #[derive(Debug, Serialize, Deserialize)]
312 pub struct Receipt<'a> {
313 #[serde(deserialize_with = "deserde_txtype")]
315 pub tx_type: TxType,
316 pub success: bool,
320 pub cumulative_gas_used: u64,
322 pub logs: Cow<'a, Vec<Log>>,
324 }
325
326 fn deserde_txtype<'de, D>(deserializer: D) -> Result<TxType, D::Error>
328 where
329 D: Deserializer<'de>,
330 {
331 let value = U8::deserialize(deserializer)?;
332 value.to::<u8>().try_into().map_err(serde::de::Error::custom)
333 }
334
335 impl<'a> From<&'a super::Receipt> for Receipt<'a> {
336 fn from(value: &'a super::Receipt) -> Self {
337 Self {
338 tx_type: value.tx_type,
339 success: value.success,
340 cumulative_gas_used: value.cumulative_gas_used,
341 logs: Cow::Borrowed(&value.logs),
342 }
343 }
344 }
345
346 impl<'a> From<Receipt<'a>> for super::Receipt {
347 fn from(value: Receipt<'a>) -> Self {
348 Self {
349 tx_type: value.tx_type,
350 success: value.success,
351 cumulative_gas_used: value.cumulative_gas_used,
352 logs: value.logs.into_owned(),
353 }
354 }
355 }
356
357 impl SerializeAs<super::Receipt> for Receipt<'_> {
358 fn serialize_as<S>(source: &super::Receipt, serializer: S) -> Result<S::Ok, S::Error>
359 where
360 S: Serializer,
361 {
362 Receipt::<'_>::from(source).serialize(serializer)
363 }
364 }
365
366 impl<'de> DeserializeAs<'de, super::Receipt> for Receipt<'de> {
367 fn deserialize_as<D>(deserializer: D) -> Result<super::Receipt, D::Error>
368 where
369 D: Deserializer<'de>,
370 {
371 Receipt::<'_>::deserialize(deserializer).map(Into::into)
372 }
373 }
374
375 impl reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat for super::Receipt {
376 type BincodeRepr<'a> = Receipt<'a>;
377
378 fn as_repr(&self) -> Self::BincodeRepr<'_> {
379 self.into()
380 }
381
382 fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
383 repr.into()
384 }
385 }
386
387 #[cfg(test)]
388 mod tests {
389 use crate::{receipt::serde_bincode_compat, Receipt};
390 use arbitrary::Arbitrary;
391 use rand::Rng;
392 use serde_with::serde_as;
393
394 #[test]
395 fn test_receipt_bincode_roundtrip() {
396 #[serde_as]
397 #[derive(Debug, PartialEq, Eq)]
398 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
399 struct Data {
400 #[serde_as(as = "serde_bincode_compat::Receipt<'_>")]
401 reseipt: Receipt,
402 }
403
404 let mut bytes = [0u8; 1024];
405 rand::rng().fill(bytes.as_mut_slice());
406 let data = Data {
407 reseipt: Receipt::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(),
408 };
409 let encoded = bincode::serialize(&data).unwrap();
410 let decoded: Data = bincode::deserialize(&encoded).unwrap();
411 assert_eq!(decoded, data);
412 }
413 }
414}
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419 use crate::TransactionSigned;
420 use alloy_eips::eip2718::Encodable2718;
421 use alloy_primitives::{
422 address, b256, bloom, bytes, hex_literal::hex, Address, Bytes, Log, LogData,
423 };
424 use alloy_rlp::Decodable;
425 use reth_codecs::Compact;
426 use reth_primitives_traits::proofs::{
427 calculate_receipt_root, calculate_transaction_root, calculate_withdrawals_root,
428 };
429
430 pub(crate) type Block<T = TransactionSigned> = alloy_consensus::Block<T>;
434
435 #[test]
436 fn test_decode_receipt() {
437 reth_codecs::test_utils::test_decode::<Receipt>(&hex!(
438 "c428b52ffd23fc42696156b10200f034792b6a94c3850215c2fef7aea361a0c31b79d9a32652eefc0d4e2e730036061cff7344b6fc6132b50cda0ed810a991ae58ef013150c12b2522533cb3b3a8b19b7786a8b5ff1d3cdc84225e22b02def168c8858df"
439 ));
440 }
441
442 #[test]
444 fn encode_legacy_receipt() {
445 let expected = hex!(
446 "f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"
447 );
448
449 let mut data = Vec::with_capacity(expected.length());
450 let receipt = ReceiptWithBloom {
451 receipt: Receipt {
452 tx_type: TxType::Legacy,
453 cumulative_gas_used: 0x1u64,
454 logs: vec![Log::new_unchecked(
455 address!("0x0000000000000000000000000000000000000011"),
456 vec![
457 b256!("0x000000000000000000000000000000000000000000000000000000000000dead"),
458 b256!("0x000000000000000000000000000000000000000000000000000000000000beef"),
459 ],
460 bytes!("0100ff"),
461 )],
462 success: false,
463 },
464 logs_bloom: [0; 256].into(),
465 };
466
467 receipt.encode(&mut data);
468
469 assert_eq!(receipt.length(), expected.len());
471 assert_eq!(data, expected);
472 }
473
474 #[test]
476 fn decode_legacy_receipt() {
477 let data = hex!(
478 "f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"
479 );
480
481 let expected = ReceiptWithBloom {
483 receipt: Receipt {
484 tx_type: TxType::Legacy,
485 cumulative_gas_used: 0x1u64,
486 logs: vec![Log::new_unchecked(
487 address!("0x0000000000000000000000000000000000000011"),
488 vec![
489 b256!("0x000000000000000000000000000000000000000000000000000000000000dead"),
490 b256!("0x000000000000000000000000000000000000000000000000000000000000beef"),
491 ],
492 bytes!("0100ff"),
493 )],
494 success: false,
495 },
496 logs_bloom: [0; 256].into(),
497 };
498
499 let receipt = ReceiptWithBloom::decode(&mut &data[..]).unwrap();
500 assert_eq!(receipt, expected);
501 }
502
503 #[test]
504 fn gigantic_receipt() {
505 let receipt = Receipt {
506 cumulative_gas_used: 16747627,
507 success: true,
508 tx_type: TxType::Legacy,
509 logs: vec![
510 Log::new_unchecked(
511 address!("0x4bf56695415f725e43c3e04354b604bcfb6dfb6e"),
512 vec![b256!(
513 "0xc69dc3d7ebff79e41f525be431d5cd3cc08f80eaf0f7819054a726eeb7086eb9"
514 )],
515 Bytes::from(vec![1; 0xffffff]),
516 ),
517 Log::new_unchecked(
518 address!("0xfaca325c86bf9c2d5b413cd7b90b209be92229c2"),
519 vec![b256!(
520 "0x8cca58667b1e9ffa004720ac99a3d61a138181963b294d270d91c53d36402ae2"
521 )],
522 Bytes::from(vec![1; 0xffffff]),
523 ),
524 ],
525 };
526
527 let mut data = vec![];
528 receipt.to_compact(&mut data);
529 let (decoded, _) = Receipt::from_compact(&data[..], data.len());
530 assert_eq!(decoded, receipt);
531 }
532
533 #[test]
534 fn test_encode_2718_length() {
535 let receipt = ReceiptWithBloom {
536 receipt: Receipt {
537 tx_type: TxType::Eip1559,
538 success: true,
539 cumulative_gas_used: 21000,
540 logs: vec![],
541 },
542 logs_bloom: Bloom::default(),
543 };
544
545 let encoded = receipt.encoded_2718();
546 assert_eq!(
547 encoded.len(),
548 receipt.encode_2718_len(),
549 "Encoded length should match the actual encoded data length"
550 );
551
552 let legacy_receipt = ReceiptWithBloom {
554 receipt: Receipt {
555 tx_type: TxType::Legacy,
556 success: true,
557 cumulative_gas_used: 21000,
558 logs: vec![],
559 },
560 logs_bloom: Bloom::default(),
561 };
562
563 let legacy_encoded = legacy_receipt.encoded_2718();
564 assert_eq!(
565 legacy_encoded.len(),
566 legacy_receipt.encode_2718_len(),
567 "Encoded length for legacy receipt should match the actual encoded data length"
568 );
569 }
570
571 #[test]
572 fn check_transaction_root() {
573 let data = &hex!(
574 "f90262f901f9a092230ce5476ae868e98c7979cfc165a93f8b6ad1922acf2df62e340916efd49da01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa02307107a867056ca33b5087e77c4174f47625e48fb49f1c70ced34890ddd88f3a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba0c598f69a5674cae9337261b669970e24abc0b46e6d284372a239ec8ccbf20b0ab901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8618203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0"
575 );
576 let block_rlp = &mut data.as_slice();
577 let block: Block = Block::decode(block_rlp).unwrap();
578
579 let tx_root = calculate_transaction_root(&block.body.transactions);
580 assert_eq!(block.transactions_root, tx_root, "Must be the same");
581 }
582
583 #[test]
584 fn check_withdrawals_root() {
585 let data = &hex!(
588 "f90238f90219a0151934ad9b654c50197f37018ee5ee9bb922dec0a1b5e24a6d679cb111cdb107a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0046119afb1ab36aaa8f66088677ed96cd62762f6d3e65642898e189fbe702d51a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff8082079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a048a703da164234812273ea083e4ec3d09d028300cd325b46a6a75402e5a7ab95c0c0d9d8808094c94f5374fce5edbc8e2a8697c15331677e6ebf0b80"
589 );
590 let block: Block = Block::decode(&mut data.as_slice()).unwrap();
591 assert!(block.body.withdrawals.is_some());
592 let withdrawals = block.body.withdrawals.as_ref().unwrap();
593 assert_eq!(withdrawals.len(), 1);
594 let withdrawals_root = calculate_withdrawals_root(withdrawals);
595 assert_eq!(block.withdrawals_root, Some(withdrawals_root));
596
597 let data = &hex!(
600 "f9028cf90219a0151934ad9b654c50197f37018ee5ee9bb922dec0a1b5e24a6d679cb111cdb107a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0ccf7b62d616c2ad7af862d67b9dcd2119a90cebbff8c3cd1e5d7fc99f8755774a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff8082079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a0a95b9a7b58a6b3cb4001eb0be67951c5517141cb0183a255b5cae027a7b10b36c0c0f86cda808094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da028094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da018094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da028094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710"
601 );
602 let block: Block = Block::decode(&mut data.as_slice()).unwrap();
603 assert!(block.body.withdrawals.is_some());
604 let withdrawals = block.body.withdrawals.as_ref().unwrap();
605 assert_eq!(withdrawals.len(), 4);
606 let withdrawals_root = calculate_withdrawals_root(withdrawals);
607 assert_eq!(block.withdrawals_root, Some(withdrawals_root));
608 }
609 #[test]
610 fn check_receipt_root_optimism() {
611 use alloy_consensus::ReceiptWithBloom;
612
613 let logs = vec![Log {
614 address: Address::ZERO,
615 data: LogData::new_unchecked(vec![], Default::default()),
616 }];
617 let bloom = bloom!(
618 "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"
619 );
620 let receipt = ReceiptWithBloom {
621 receipt: Receipt {
622 tx_type: TxType::Eip2930,
623 success: true,
624 cumulative_gas_used: 102068,
625 logs,
626 },
627 logs_bloom: bloom,
628 };
629 let receipt = vec![receipt];
630 let root = calculate_receipt_root(&receipt);
631 assert_eq!(
632 root,
633 b256!("0xfe70ae4a136d98944951b2123859698d59ad251a381abc9960fa81cae3d0d4a0")
634 );
635 }
636}