1use bytes::Bytes;
2use ethereum_types::{Address, Bloom, BloomInput, H256};
3use ethrex_crypto::Crypto;
4use ethrex_rlp::{
5 decode::{RLPDecode, get_rlp_bytes_item_payload, is_encoded_as_bytes},
6 encode::RLPEncode,
7 error::RLPDecodeError,
8 structs::{Decoder, Encoder},
9};
10use serde::{Deserialize, Serialize};
11
12use crate::types::TxType;
13pub type Index = u64;
14
15pub const FRAME_RECEIPT_STATUS_FAILURE: u8 = 0;
18pub const FRAME_RECEIPT_STATUS_SUCCESS: u8 = 1;
19pub const FRAME_RECEIPT_STATUS_SKIPPED: u8 = 2;
20
21#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
23pub struct FrameReceipt {
24 pub status: u8,
25 pub gas_used: u64,
26 pub logs: Vec<Log>,
27}
28
29impl RLPEncode for FrameReceipt {
30 fn encode(&self, buf: &mut dyn bytes::BufMut) {
31 Encoder::new(buf)
32 .encode_field(&self.status)
33 .encode_field(&self.gas_used)
34 .encode_field(&self.logs)
35 .finish();
36 }
37}
38
39impl RLPDecode for FrameReceipt {
40 fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
41 let decoder = Decoder::new(rlp)?;
42 let (status, decoder) = decoder.decode_field("status")?;
43 let (gas_used, decoder) = decoder.decode_field("gas_used")?;
44 let (logs, decoder) = decoder.decode_field("logs")?;
45 Ok((
46 FrameReceipt {
47 status,
48 gas_used,
49 logs,
50 },
51 decoder.finish()?,
52 ))
53 }
54}
55
56#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
58pub struct Receipt {
59 pub tx_type: TxType,
60 pub succeeded: bool,
61 pub cumulative_gas_used: u64,
65 pub logs: Vec<Log>,
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub payer: Option<Address>,
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub frame_receipts: Option<Vec<FrameReceipt>>,
72}
73
74impl Receipt {
75 pub fn new(tx_type: TxType, succeeded: bool, cumulative_gas_used: u64, logs: Vec<Log>) -> Self {
76 Self {
77 tx_type,
78 succeeded,
79 cumulative_gas_used,
80 logs,
81 payer: None,
82 frame_receipts: None,
83 }
84 }
85
86 pub fn encode_inner(&self) -> Vec<u8> {
87 let mut encoded_data = vec![];
88 let tx_type: u8 = self.tx_type as u8;
89 if self.tx_type == TxType::Frame {
90 let empty_frame_receipts = Vec::new();
93 Encoder::new(&mut encoded_data)
94 .encode_field(&tx_type)
95 .encode_field(&self.cumulative_gas_used)
96 .encode_field(&self.payer.unwrap_or_default())
97 .encode_field(
98 self.frame_receipts
99 .as_ref()
100 .unwrap_or(&empty_frame_receipts),
101 )
102 .finish();
103 } else {
104 Encoder::new(&mut encoded_data)
105 .encode_field(&tx_type)
106 .encode_field(&self.succeeded)
107 .encode_field(&self.cumulative_gas_used)
108 .encode_field(&self.logs)
109 .finish();
110 }
111 encoded_data
112 }
113
114 pub fn encode_storage(&self) -> Vec<u8> {
122 if self.tx_type == TxType::Frame {
123 let mut buf = vec![];
124 let empty_frame_receipts = Vec::new();
125 Encoder::new(&mut buf)
126 .encode_field(&(self.tx_type as u8))
127 .encode_field(&self.succeeded)
128 .encode_field(&self.cumulative_gas_used)
129 .encode_field(&self.logs)
130 .encode_field(&self.payer.unwrap_or_default())
131 .encode_field(
132 self.frame_receipts
133 .as_ref()
134 .unwrap_or(&empty_frame_receipts),
135 )
136 .finish();
137 buf
138 } else {
139 self.encode_inner()
140 }
141 }
142
143 pub fn decode_storage(rlp: &[u8]) -> Result<Receipt, RLPDecodeError> {
145 let (tx_type_byte, _): (u8, _) = Decoder::new(rlp)?.decode_field("tx-type")?;
147 if TxType::from_u8(tx_type_byte) != Some(TxType::Frame) {
148 return Receipt::decode(rlp);
151 }
152 let decoder = Decoder::new(rlp)?;
153 let (_, decoder): (u8, _) = decoder.decode_field("tx-type")?;
154 let (succeeded, decoder) = decoder.decode_field("succeeded")?;
155 let (cumulative_gas_used, decoder) = decoder.decode_field("cumulative_gas_used")?;
156 let (logs, decoder) = decoder.decode_field("logs")?;
157 let (payer, decoder): (Address, _) = decoder.decode_field("payer")?;
158 let (frame_receipts, decoder): (Vec<FrameReceipt>, _) =
159 decoder.decode_field("frame_receipts")?;
160 decoder.finish()?;
161 Ok(Receipt {
162 tx_type: TxType::Frame,
163 succeeded,
164 cumulative_gas_used,
165 logs,
166 payer: if payer == Address::zero() {
167 None
168 } else {
169 Some(payer)
170 },
171 frame_receipts: Some(frame_receipts),
172 })
173 }
174
175 pub fn encode_inner_with_bloom(&self, crypto: &dyn Crypto) -> Vec<u8> {
176 self.encode_inner_with_precomputed_bloom(bloom_from_logs(&self.logs, crypto))
177 }
178
179 pub fn encode_inner_with_precomputed_bloom(&self, bloom: Bloom) -> Vec<u8> {
183 let mut encode_buf = Vec::with_capacity(512);
186 if self.tx_type != TxType::Legacy {
187 encode_buf.push(self.tx_type as u8);
188 }
189 if self.tx_type == TxType::Frame {
190 let empty_frame_receipts = Vec::new();
194 Encoder::new(&mut encode_buf)
195 .encode_field(&self.cumulative_gas_used)
196 .encode_field(&self.payer.unwrap_or_default())
197 .encode_field(
198 self.frame_receipts
199 .as_ref()
200 .unwrap_or(&empty_frame_receipts),
201 )
202 .finish();
203 } else {
204 Encoder::new(&mut encode_buf)
205 .encode_field(&self.succeeded)
206 .encode_field(&self.cumulative_gas_used)
207 .encode_field(&bloom)
208 .encode_field(&self.logs)
209 .finish();
210 }
211 encode_buf
212 }
213
214 pub fn decode_inner_with_bloom(rlp: &[u8]) -> Result<(Receipt, &[u8]), RLPDecodeError> {
228 let (tx_type, payload) = match rlp.first() {
232 Some(byte) if *byte < 0x7f => {
233 let Some(tx_type) = TxType::from_u8(*byte) else {
234 return Err(RLPDecodeError::Custom(format!(
235 "Invalid transaction type: {byte}"
236 )));
237 };
238 (tx_type, &rlp[1..])
239 }
240 _ => (TxType::Legacy, rlp),
241 };
242
243 if tx_type == TxType::Frame {
244 let decoder = Decoder::new(payload)?;
245 let (cumulative_gas_used, decoder) = decoder.decode_field("cumulative_gas_used")?;
246 let (payer, decoder): (Address, _) = decoder.decode_field("payer")?;
247 let (frame_receipts, decoder): (Vec<FrameReceipt>, _) =
248 decoder.decode_field("frame_receipts")?;
249 let remaining = decoder.finish_unchecked();
250 let payer = if payer == Address::zero() {
251 None
252 } else {
253 Some(payer)
254 };
255 let succeeded = frame_receipts
258 .iter()
259 .all(|fr| fr.status == FRAME_RECEIPT_STATUS_SUCCESS);
260 Ok((
261 Receipt {
262 tx_type,
263 succeeded,
264 cumulative_gas_used,
265 logs: Vec::new(),
266 payer,
267 frame_receipts: Some(frame_receipts),
268 },
269 remaining,
270 ))
271 } else {
272 let decoder = Decoder::new(payload)?;
273 let (succeeded, decoder) = decoder.decode_field("succeeded")?;
274 let (cumulative_gas_used, decoder) = decoder.decode_field("cumulative_gas_used")?;
275 let (_bloom, decoder): (Bloom, _) = decoder.decode_field("bloom")?;
278 let (logs, decoder) = decoder.decode_field("logs")?;
279 let remaining = decoder.finish_unchecked();
280 Ok((
281 Receipt {
282 tx_type,
283 succeeded,
284 cumulative_gas_used,
285 logs,
286 payer: None,
287 frame_receipts: None,
288 },
289 remaining,
290 ))
291 }
292 }
293}
294
295pub fn bloom_from_logs(logs: &[Log], crypto: &dyn Crypto) -> Bloom {
296 let mut bloom = Bloom::zero();
297 for log in logs {
298 let address_hash = crypto.keccak256(log.address.as_bytes());
299 bloom.accrue(BloomInput::Hash(&address_hash));
300 for topic in log.topics.iter() {
301 let topic_hash = crypto.keccak256(topic.as_bytes());
302 bloom.accrue(BloomInput::Hash(&topic_hash));
303 }
304 }
305 bloom
306}
307
308impl RLPEncode for Receipt {
309 fn encode(&self, buf: &mut dyn bytes::BufMut) {
310 let encoded_inner = self.encode_inner();
311 buf.put_slice(&encoded_inner);
312 }
313}
314
315impl RLPDecode for Receipt {
316 fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
317 let decoder = Decoder::new(rlp)?;
318 let (tx_type, decoder): (u8, _) = decoder.decode_field("tx-type")?;
319
320 let Some(tx_type) = TxType::from_u8(tx_type) else {
321 return Err(RLPDecodeError::Custom(
322 "Invalid transaction type".to_string(),
323 ));
324 };
325
326 if tx_type == TxType::Frame {
327 let (cumulative_gas_used, decoder) = decoder.decode_field("cumulative_gas_used")?;
329 let (payer, decoder): (Address, _) = decoder.decode_field("payer")?;
330 let (frame_receipts, decoder): (Vec<FrameReceipt>, _) =
331 decoder.decode_field("frame_receipts")?;
332 let payer = if payer == Address::zero() {
333 None
334 } else {
335 Some(payer)
336 };
337 let succeeded = frame_receipts
340 .iter()
341 .all(|fr| fr.status == FRAME_RECEIPT_STATUS_SUCCESS);
342 Ok((
343 Receipt {
344 tx_type,
345 succeeded,
346 cumulative_gas_used,
347 logs: Vec::new(),
348 payer,
349 frame_receipts: Some(frame_receipts),
350 },
351 decoder.finish()?,
352 ))
353 } else {
354 let (succeeded, decoder) = decoder.decode_field("succeeded")?;
355 let (cumulative_gas_used, decoder) = decoder.decode_field("cumulative_gas_used")?;
356 let (logs, decoder) = decoder.decode_field("logs")?;
357 Ok((
358 Receipt {
359 tx_type,
360 succeeded,
361 cumulative_gas_used,
362 logs,
363 payer: None,
364 frame_receipts: None,
365 },
366 decoder.finish()?,
367 ))
368 }
369 }
370}
371
372#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
374pub struct ReceiptWithBloom {
375 pub tx_type: TxType,
376 pub succeeded: bool,
377 pub cumulative_gas_used: u64,
381 pub bloom: Bloom,
382 pub logs: Vec<Log>,
383}
384
385impl ReceiptWithBloom {
386 pub fn new(tx_type: TxType, succeeded: bool, cumulative_gas_used: u64, logs: Vec<Log>) -> Self {
387 Self {
388 tx_type,
389 succeeded,
390 cumulative_gas_used,
391 bloom: bloom_from_logs(&logs, ðrex_crypto::NativeCrypto),
392 logs,
393 }
394 }
395
396 pub fn encode_inner(&self) -> Vec<u8> {
414 let mut encode_buff = match self.tx_type {
415 TxType::Legacy => {
416 vec![]
417 }
418 _ => {
419 vec![self.tx_type as u8]
420 }
421 };
422 Encoder::new(&mut encode_buff)
423 .encode_field(&self.succeeded)
424 .encode_field(&self.cumulative_gas_used)
425 .encode_field(&self.bloom)
426 .encode_field(&self.logs)
427 .finish();
428 encode_buff
429 }
430
431 pub fn decode_inner(rlp: &[u8]) -> Result<Self, RLPDecodeError> {
435 let (tx_type, rlp) = match rlp.first() {
437 Some(tx_type) if *tx_type < 0x7f => {
438 let tx_type = match tx_type {
439 0x0 => TxType::Legacy,
440 0x1 => TxType::EIP2930,
441 0x2 => TxType::EIP1559,
442 0x3 => TxType::EIP4844,
443 0x4 => TxType::EIP7702,
444 0x6 => TxType::Frame,
445 0x7d => TxType::FeeToken,
446 0x7e => TxType::Privileged,
447 ty => {
448 return Err(RLPDecodeError::Custom(format!(
449 "Invalid transaction type: {ty}"
450 )));
451 }
452 };
453 (tx_type, &rlp[1..])
454 }
455 _ => (TxType::Legacy, rlp),
456 };
457 let decoder = Decoder::new(rlp)?;
458 let (succeeded, decoder) = decoder.decode_field("succeeded")?;
459 let (cumulative_gas_used, decoder) = decoder.decode_field("cumulative_gas_used")?;
460 let (bloom, decoder) = decoder.decode_field("bloom")?;
461 let (logs, decoder) = decoder.decode_field("logs")?;
462 decoder.finish()?;
463
464 Ok(Self {
465 tx_type,
466 succeeded,
467 cumulative_gas_used,
468 bloom,
469 logs,
470 })
471 }
472}
473
474impl RLPEncode for ReceiptWithBloom {
475 fn encode(&self, buf: &mut dyn bytes::BufMut) {
479 match self.tx_type {
480 TxType::Legacy => {
481 let legacy_encoded = self.encode_inner();
482 buf.put_slice(&legacy_encoded);
483 }
484 _ => {
485 let typed_recepipt_encoded = self.encode_inner();
486 let bytes = Bytes::from(typed_recepipt_encoded);
487 bytes.encode(buf);
488 }
489 };
490 }
491}
492
493impl RLPDecode for ReceiptWithBloom {
494 fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
498 let (tx_type, rlp) = if is_encoded_as_bytes(rlp)? {
501 let payload = get_rlp_bytes_item_payload(rlp)?;
502 let tx_type = match payload.first().ok_or(RLPDecodeError::InvalidLength)? {
503 0x0 => TxType::Legacy,
504 0x1 => TxType::EIP2930,
505 0x2 => TxType::EIP1559,
506 0x3 => TxType::EIP4844,
507 0x4 => TxType::EIP7702,
508 0x6 => TxType::Frame,
509 0x7d => TxType::FeeToken,
510 0x7e => TxType::Privileged,
511 ty => {
512 return Err(RLPDecodeError::Custom(format!(
513 "Invalid transaction type: {ty}"
514 )));
515 }
516 };
517 (tx_type, &payload[1..])
518 } else {
519 (TxType::Legacy, rlp)
520 };
521
522 let decoder = Decoder::new(rlp)?;
523 let (succeeded, decoder) = decoder.decode_field("succeeded")?;
524 let (cumulative_gas_used, decoder) = decoder.decode_field("cumulative_gas_used")?;
525 let (bloom, decoder) = decoder.decode_field("bloom")?;
526 let (logs, decoder) = decoder.decode_field("logs")?;
527
528 Ok((
529 ReceiptWithBloom {
530 tx_type,
531 succeeded,
532 cumulative_gas_used,
533 bloom,
534 logs,
535 },
536 decoder.finish()?,
537 ))
538 }
539}
540
541impl From<&Receipt> for ReceiptWithBloom {
542 fn from(receipt: &Receipt) -> Self {
543 Self {
544 tx_type: receipt.tx_type,
545 succeeded: receipt.succeeded,
546 cumulative_gas_used: receipt.cumulative_gas_used,
547 bloom: bloom_from_logs(&receipt.logs, ðrex_crypto::NativeCrypto),
548 logs: receipt.logs.clone(),
549 }
550 }
551}
552
553impl From<&ReceiptWithBloom> for Receipt {
554 fn from(receipt: &ReceiptWithBloom) -> Self {
555 Self {
556 tx_type: receipt.tx_type,
557 succeeded: receipt.succeeded,
558 cumulative_gas_used: receipt.cumulative_gas_used,
559 logs: receipt.logs.clone(),
560 payer: None,
561 frame_receipts: None,
562 }
563 }
564}
565
566#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
568pub struct Log {
569 pub address: Address,
570 pub topics: Vec<H256>,
571 pub data: Bytes,
572}
573
574impl RLPEncode for Log {
575 fn encode(&self, buf: &mut dyn bytes::BufMut) {
576 Encoder::new(buf)
577 .encode_field(&self.address)
578 .encode_field(&self.topics)
579 .encode_field(&self.data)
580 .finish();
581 }
582}
583
584impl RLPDecode for Log {
585 fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
586 let decoder = Decoder::new(rlp)?;
587 let (address, decoder) = decoder.decode_field("address")?;
588 let (topics, decoder) = decoder.decode_field("topics")?;
589 let (data, decoder) = decoder.decode_field("data")?;
590 let log = Log {
591 address,
592 topics,
593 data,
594 };
595 Ok((log, decoder.finish()?))
596 }
597}
598
599#[cfg(test)]
600mod test {
601 use super::*;
602
603 fn h256_from_hex(s: &str) -> H256 {
604 H256::from_slice(&hex::decode(s).unwrap())
605 }
606
607 #[test]
617 fn pre_byzantium_receipt_cannot_be_decoded() {
618 fn encode_with_first_field<T: RLPEncode>(first: &T) -> Vec<u8> {
625 let mut buf = Vec::new();
626 Encoder::new(&mut buf)
627 .encode_field(first)
628 .encode_field(&21_000u64)
629 .encode_field(&Bloom::zero())
630 .encode_field(&Vec::<Log>::new())
631 .finish();
632 buf
633 }
634
635 let post_byzantium = encode_with_first_field(&true);
636 assert!(
637 Receipt::decode_inner_with_bloom(&post_byzantium).is_ok(),
638 "a status-flag (post-Byzantium) receipt must decode"
639 );
640
641 let pre_byzantium = encode_with_first_field(&H256::from([0x11u8; 32]));
642 assert!(
643 Receipt::decode_inner_with_bloom(&pre_byzantium).is_err(),
644 "a post-state-root (pre-Byzantium) receipt must not decode"
645 );
646 }
647
648 #[test]
649 fn test_encode_decode_receipt_legacy() {
650 let receipt = Receipt {
651 tx_type: TxType::Legacy,
652 succeeded: true,
653 cumulative_gas_used: 1200,
654 logs: vec![Log {
655 address: Address::random(),
656 topics: vec![],
657 data: Bytes::from_static(b"foo"),
658 }],
659 payer: None,
660 frame_receipts: None,
661 };
662 let encoded_receipt = receipt.encode_to_vec();
663 assert_eq!(receipt, Receipt::decode(&encoded_receipt).unwrap())
664 }
665
666 #[test]
667 fn test_encode_decode_receipt_non_legacy() {
668 let receipt = Receipt {
669 tx_type: TxType::EIP4844,
670 succeeded: true,
671 cumulative_gas_used: 1500,
672 logs: vec![Log {
673 address: Address::random(),
674 topics: vec![],
675 data: Bytes::from_static(b"bar"),
676 }],
677 payer: None,
678 frame_receipts: None,
679 };
680 let encoded_receipt = receipt.encode_to_vec();
681 assert_eq!(receipt, Receipt::decode(&encoded_receipt).unwrap())
682 }
683
684 #[test]
685 fn test_encode_decode_inner_receipt_legacy() {
686 let receipt = ReceiptWithBloom {
687 tx_type: TxType::Legacy,
688 succeeded: true,
689 cumulative_gas_used: 1200,
690 bloom: Bloom::random(),
691 logs: vec![Log {
692 address: Address::random(),
693 topics: vec![],
694 data: Bytes::from_static(b"foo"),
695 }],
696 };
697 let encoded_receipt = receipt.encode_inner();
698 assert_eq!(
699 receipt,
700 ReceiptWithBloom::decode_inner(&encoded_receipt).unwrap()
701 )
702 }
703
704 #[test]
705 fn test_encode_decode_receipt_inner_non_legacy() {
706 let receipt = ReceiptWithBloom {
707 tx_type: TxType::EIP4844,
708 succeeded: true,
709 cumulative_gas_used: 1500,
710 bloom: Bloom::random(),
711 logs: vec![Log {
712 address: Address::random(),
713 topics: vec![],
714 data: Bytes::from_static(b"bar"),
715 }],
716 };
717 let encoded_receipt = receipt.encode_inner();
718 assert_eq!(
719 receipt,
720 ReceiptWithBloom::decode_inner(&encoded_receipt).unwrap()
721 )
722 }
723
724 #[test]
725 fn test_encode_receipt_with_bloom() {
726 let receipt = Receipt {
727 tx_type: TxType::EIP1559,
728 succeeded: true,
729 cumulative_gas_used: 1500,
730 logs: vec![Log {
731 address: Address::random(),
732 topics: vec![
733 h256_from_hex(
734 "e70c0d1060ffbafc84e0e18d028245de3deeb0f41ecbade6562fa657d85ae945",
735 ),
736 h256_from_hex(
737 "e7e9cd61c8c6cb313324d785aa130fe50a7b9885e4d1d7700a327c5e9ae4e183",
738 ),
739 h256_from_hex(
740 "666d827b9db958c08f7186f127e3d9ea6a97288bcc4b527951ce493f6e2b76c4",
741 ),
742 h256_from_hex(
743 "28b4366544dccafad7b61138e9ada51706e85bb217a20cfa1c86e2648f8f369a",
744 ),
745 h256_from_hex(
746 "85cf9717f65c70d71cc6175f653512c13ce7b6a9bc5d9c2b9c49b2d2d6cb9536",
747 ),
748 ],
749 data: Bytes::from_static(b"bar"),
750 }],
751 payer: None,
752 frame_receipts: None,
753 };
754 let encoded_receipt = receipt.encode_inner_with_bloom(ðrex_crypto::NativeCrypto);
755
756 let correct_bloom = {
757 let mut bloom = Bloom::zero();
758 for log in receipt.logs {
759 bloom.accrue(BloomInput::Raw(log.address.as_ref()));
760 for topic in log.topics.iter() {
761 bloom.accrue(BloomInput::Raw(topic.as_ref()));
762 }
763 }
764 bloom
765 };
766 let receipt_with_bloom = ReceiptWithBloom::decode_inner(&encoded_receipt).unwrap();
767 assert_eq!(receipt_with_bloom.bloom, correct_bloom);
768 }
769
770 #[test]
771 fn test_frame_receipt_rlp_roundtrip() {
772 let fr = FrameReceipt {
773 status: FRAME_RECEIPT_STATUS_SUCCESS,
774 gas_used: 21000,
775 logs: vec![Log {
776 address: Address::random(),
777 topics: vec![],
778 data: Bytes::from_static(b"test"),
779 }],
780 };
781 let encoded = fr.encode_to_vec();
782 let decoded = FrameReceipt::decode(&encoded).unwrap();
783 assert_eq!(fr, decoded);
784 }
785
786 #[test]
787 fn test_frame_receipt_skipped_status_rlp_roundtrip() {
788 let fr = FrameReceipt {
790 status: FRAME_RECEIPT_STATUS_SKIPPED,
791 gas_used: 0,
792 logs: vec![],
793 };
794 let encoded = fr.encode_to_vec();
795 let decoded = FrameReceipt::decode(&encoded).unwrap();
796 assert_eq!(fr, decoded);
797 assert_eq!(decoded.status, FRAME_RECEIPT_STATUS_SKIPPED);
798 }
799
800 #[test]
801 fn test_receipt_with_frame_fields_rlp_roundtrip() {
802 let receipt = Receipt {
806 tx_type: TxType::Frame,
807 succeeded: true,
808 cumulative_gas_used: 315000,
809 logs: vec![],
810 payer: Some(Address::from_low_u64_be(0x1234)),
811 frame_receipts: Some(vec![
812 FrameReceipt {
813 status: FRAME_RECEIPT_STATUS_SUCCESS,
814 gas_used: 100000,
815 logs: vec![],
816 },
817 FrameReceipt {
818 status: FRAME_RECEIPT_STATUS_SUCCESS,
819 gas_used: 200000,
820 logs: vec![Log {
821 address: Address::from_low_u64_be(0xbeef),
822 topics: vec![],
823 data: Bytes::from_static(b"frame2"),
824 }],
825 },
826 ]),
827 };
828 let encoded = receipt.encode_to_vec();
829 let decoded = Receipt::decode(&encoded).unwrap();
830 assert_eq!(decoded.tx_type, TxType::Frame);
831 assert_eq!(decoded.cumulative_gas_used, 315000);
832 assert_eq!(decoded.payer, Some(Address::from_low_u64_be(0x1234)));
833 assert!(decoded.succeeded); assert!(decoded.logs.is_empty()); assert_eq!(decoded.frame_receipts, receipt.frame_receipts);
836 }
837
838 #[test]
839 fn test_frame_receipt_succeeded_derived_from_frames() {
840 let receipt = Receipt {
841 tx_type: TxType::Frame,
842 succeeded: true, cumulative_gas_used: 100000,
844 logs: vec![],
845 payer: Some(Address::from_low_u64_be(0x1)),
846 frame_receipts: Some(vec![
847 FrameReceipt {
848 status: FRAME_RECEIPT_STATUS_SUCCESS,
849 gas_used: 50000,
850 logs: vec![],
851 },
852 FrameReceipt {
853 status: FRAME_RECEIPT_STATUS_FAILURE,
854 gas_used: 50000,
855 logs: vec![],
856 },
857 ]),
858 };
859 let encoded = receipt.encode_to_vec();
860 let decoded = Receipt::decode(&encoded).unwrap();
861 assert!(!decoded.succeeded); }
863
864 #[test]
865 fn test_frame_receipt_skipped_disqualifies_succeeded() {
866 let receipt = Receipt {
869 tx_type: TxType::Frame,
870 succeeded: false,
871 cumulative_gas_used: 100000,
872 logs: vec![],
873 payer: Some(Address::from_low_u64_be(0x1)),
874 frame_receipts: Some(vec![
875 FrameReceipt {
876 status: FRAME_RECEIPT_STATUS_SUCCESS,
877 gas_used: 50000,
878 logs: vec![],
879 },
880 FrameReceipt {
881 status: FRAME_RECEIPT_STATUS_SKIPPED,
882 gas_used: 0,
883 logs: vec![],
884 },
885 ]),
886 };
887 let encoded = receipt.encode_to_vec();
888 let decoded = Receipt::decode(&encoded).unwrap();
889 assert!(!decoded.succeeded);
890 }
891
892 #[test]
893 fn frame_receipt_storage_roundtrip_preserves_logs_and_status() {
894 let log = Log {
895 address: Address::from_low_u64_be(1),
896 topics: vec![],
897 data: Bytes::from(vec![1u8]),
898 };
899 let receipt = Receipt {
900 tx_type: TxType::Frame,
901 succeeded: true, cumulative_gas_used: 50_000,
903 logs: vec![log.clone()], payer: Some(Address::from_low_u64_be(2)),
905 frame_receipts: Some(vec![
906 FrameReceipt {
907 status: FRAME_RECEIPT_STATUS_FAILURE,
908 gas_used: 1000,
909 logs: vec![],
910 }, FrameReceipt {
912 status: FRAME_RECEIPT_STATUS_SUCCESS,
913 gas_used: 2000,
914 logs: vec![log],
915 },
916 ]),
917 };
918 let decoded = Receipt::decode_storage(&receipt.encode_storage()).unwrap();
921 assert_eq!(decoded, receipt);
922 }
923
924 #[test]
925 fn nonframe_receipt_storage_roundtrip_unchanged() {
926 let r = Receipt::new(TxType::EIP1559, true, 21000, vec![]);
927 assert_eq!(Receipt::decode_storage(&r.encode_storage()).unwrap(), r);
928 assert_eq!(r.encode_storage(), r.encode_inner());
930 }
931
932 #[test]
933 fn frame_receipt_trie_encoding_is_eip8141_payload() {
934 let receipt = Receipt {
935 tx_type: TxType::Frame,
936 succeeded: true,
937 cumulative_gas_used: 100_000,
938 logs: Vec::new(),
939 payer: Some(Address::from_low_u64_be(0xBEEF)),
940 frame_receipts: Some(vec![FrameReceipt {
941 status: FRAME_RECEIPT_STATUS_SUCCESS,
942 gas_used: 21_000,
943 logs: Vec::new(),
944 }]),
945 };
946 let encoded = receipt.encode_inner_with_bloom(ðrex_crypto::NativeCrypto);
947 assert_eq!(encoded[0], 0x06);
949 let mut expected_payload = Vec::new();
951 Encoder::new(&mut expected_payload)
952 .encode_field(&100_000u64)
953 .encode_field(&Address::from_low_u64_be(0xBEEF))
954 .encode_field(receipt.frame_receipts.as_ref().unwrap())
955 .finish();
956 assert_eq!(&encoded[1..], &expected_payload[..]);
957 }
958
959 #[test]
960 fn decode_inner_with_bloom_roundtrips_nonframe() {
961 let receipt = Receipt {
965 tx_type: TxType::EIP1559,
966 succeeded: true,
967 cumulative_gas_used: 54321,
968 logs: vec![Log {
969 address: Address::from_low_u64_be(0xabcd),
970 topics: vec![h256_from_hex(
971 "e70c0d1060ffbafc84e0e18d028245de3deeb0f41ecbade6562fa657d85ae945",
972 )],
973 data: Bytes::from_static(b"hello"),
974 }],
975 payer: None,
976 frame_receipts: None,
977 };
978 let encoded = receipt.encode_inner_with_bloom(ðrex_crypto::NativeCrypto);
979 let (decoded, rest) = Receipt::decode_inner_with_bloom(&encoded).unwrap();
980 assert!(rest.is_empty());
981 assert_eq!(decoded, receipt);
982 }
983
984 #[test]
985 fn decode_inner_with_bloom_roundtrips_legacy() {
986 let receipt = Receipt {
987 tx_type: TxType::Legacy,
988 succeeded: false,
989 cumulative_gas_used: 100,
990 logs: vec![],
991 payer: None,
992 frame_receipts: None,
993 };
994 let encoded = receipt.encode_inner_with_bloom(ðrex_crypto::NativeCrypto);
995 let (decoded, rest) = Receipt::decode_inner_with_bloom(&encoded).unwrap();
996 assert!(rest.is_empty());
997 assert_eq!(decoded, receipt);
998 }
999
1000 #[test]
1001 fn decode_inner_with_bloom_roundtrips_frame_preserving_payer_and_frames() {
1002 let receipt = Receipt {
1005 tx_type: TxType::Frame,
1006 succeeded: true,
1007 cumulative_gas_used: 315000,
1008 logs: vec![],
1009 payer: Some(Address::from_low_u64_be(0x1234)),
1010 frame_receipts: Some(vec![
1011 FrameReceipt {
1012 status: FRAME_RECEIPT_STATUS_SUCCESS,
1013 gas_used: 100000,
1014 logs: vec![],
1015 },
1016 FrameReceipt {
1017 status: FRAME_RECEIPT_STATUS_SUCCESS,
1018 gas_used: 200000,
1019 logs: vec![Log {
1020 address: Address::from_low_u64_be(0xbeef),
1021 topics: vec![],
1022 data: Bytes::from_static(b"frame2"),
1023 }],
1024 },
1025 ]),
1026 };
1027 let encoded = receipt.encode_inner_with_bloom(ðrex_crypto::NativeCrypto);
1028 let (decoded, rest) = Receipt::decode_inner_with_bloom(&encoded).unwrap();
1029 assert!(rest.is_empty());
1030 assert_eq!(decoded.tx_type, TxType::Frame);
1031 assert_eq!(decoded.cumulative_gas_used, 315000);
1032 assert_eq!(decoded.payer, Some(Address::from_low_u64_be(0x1234)));
1033 assert_eq!(decoded.frame_receipts, receipt.frame_receipts);
1034 assert!(decoded.succeeded); assert!(decoded.logs.is_empty());
1036 assert_eq!(decoded, receipt);
1038 }
1039
1040 #[test]
1041 fn decode_inner_with_bloom_frame_zero_payer_maps_to_none() {
1042 let receipt = Receipt {
1043 tx_type: TxType::Frame,
1044 succeeded: true,
1045 cumulative_gas_used: 21000,
1046 logs: vec![],
1047 payer: None,
1048 frame_receipts: Some(vec![FrameReceipt {
1049 status: FRAME_RECEIPT_STATUS_SUCCESS,
1050 gas_used: 21000,
1051 logs: vec![],
1052 }]),
1053 };
1054 let encoded = receipt.encode_inner_with_bloom(ðrex_crypto::NativeCrypto);
1055 let (decoded, _) = Receipt::decode_inner_with_bloom(&encoded).unwrap();
1056 assert_eq!(decoded.payer, None);
1057 assert_eq!(decoded, receipt);
1058 }
1059}