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 = 3;
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]
608 fn test_encode_decode_receipt_legacy() {
609 let receipt = Receipt {
610 tx_type: TxType::Legacy,
611 succeeded: true,
612 cumulative_gas_used: 1200,
613 logs: vec![Log {
614 address: Address::random(),
615 topics: vec![],
616 data: Bytes::from_static(b"foo"),
617 }],
618 payer: None,
619 frame_receipts: None,
620 };
621 let encoded_receipt = receipt.encode_to_vec();
622 assert_eq!(receipt, Receipt::decode(&encoded_receipt).unwrap())
623 }
624
625 #[test]
626 fn test_encode_decode_receipt_non_legacy() {
627 let receipt = Receipt {
628 tx_type: TxType::EIP4844,
629 succeeded: true,
630 cumulative_gas_used: 1500,
631 logs: vec![Log {
632 address: Address::random(),
633 topics: vec![],
634 data: Bytes::from_static(b"bar"),
635 }],
636 payer: None,
637 frame_receipts: None,
638 };
639 let encoded_receipt = receipt.encode_to_vec();
640 assert_eq!(receipt, Receipt::decode(&encoded_receipt).unwrap())
641 }
642
643 #[test]
644 fn test_encode_decode_inner_receipt_legacy() {
645 let receipt = ReceiptWithBloom {
646 tx_type: TxType::Legacy,
647 succeeded: true,
648 cumulative_gas_used: 1200,
649 bloom: Bloom::random(),
650 logs: vec![Log {
651 address: Address::random(),
652 topics: vec![],
653 data: Bytes::from_static(b"foo"),
654 }],
655 };
656 let encoded_receipt = receipt.encode_inner();
657 assert_eq!(
658 receipt,
659 ReceiptWithBloom::decode_inner(&encoded_receipt).unwrap()
660 )
661 }
662
663 #[test]
664 fn test_encode_decode_receipt_inner_non_legacy() {
665 let receipt = ReceiptWithBloom {
666 tx_type: TxType::EIP4844,
667 succeeded: true,
668 cumulative_gas_used: 1500,
669 bloom: Bloom::random(),
670 logs: vec![Log {
671 address: Address::random(),
672 topics: vec![],
673 data: Bytes::from_static(b"bar"),
674 }],
675 };
676 let encoded_receipt = receipt.encode_inner();
677 assert_eq!(
678 receipt,
679 ReceiptWithBloom::decode_inner(&encoded_receipt).unwrap()
680 )
681 }
682
683 #[test]
684 fn test_encode_receipt_with_bloom() {
685 let receipt = Receipt {
686 tx_type: TxType::EIP1559,
687 succeeded: true,
688 cumulative_gas_used: 1500,
689 logs: vec![Log {
690 address: Address::random(),
691 topics: vec![
692 h256_from_hex(
693 "e70c0d1060ffbafc84e0e18d028245de3deeb0f41ecbade6562fa657d85ae945",
694 ),
695 h256_from_hex(
696 "e7e9cd61c8c6cb313324d785aa130fe50a7b9885e4d1d7700a327c5e9ae4e183",
697 ),
698 h256_from_hex(
699 "666d827b9db958c08f7186f127e3d9ea6a97288bcc4b527951ce493f6e2b76c4",
700 ),
701 h256_from_hex(
702 "28b4366544dccafad7b61138e9ada51706e85bb217a20cfa1c86e2648f8f369a",
703 ),
704 h256_from_hex(
705 "85cf9717f65c70d71cc6175f653512c13ce7b6a9bc5d9c2b9c49b2d2d6cb9536",
706 ),
707 ],
708 data: Bytes::from_static(b"bar"),
709 }],
710 payer: None,
711 frame_receipts: None,
712 };
713 let encoded_receipt = receipt.encode_inner_with_bloom(ðrex_crypto::NativeCrypto);
714
715 let correct_bloom = {
716 let mut bloom = Bloom::zero();
717 for log in receipt.logs {
718 bloom.accrue(BloomInput::Raw(log.address.as_ref()));
719 for topic in log.topics.iter() {
720 bloom.accrue(BloomInput::Raw(topic.as_ref()));
721 }
722 }
723 bloom
724 };
725 let receipt_with_bloom = ReceiptWithBloom::decode_inner(&encoded_receipt).unwrap();
726 assert_eq!(receipt_with_bloom.bloom, correct_bloom);
727 }
728
729 #[test]
730 fn test_frame_receipt_rlp_roundtrip() {
731 let fr = FrameReceipt {
732 status: FRAME_RECEIPT_STATUS_SUCCESS,
733 gas_used: 21000,
734 logs: vec![Log {
735 address: Address::random(),
736 topics: vec![],
737 data: Bytes::from_static(b"test"),
738 }],
739 };
740 let encoded = fr.encode_to_vec();
741 let decoded = FrameReceipt::decode(&encoded).unwrap();
742 assert_eq!(fr, decoded);
743 }
744
745 #[test]
746 fn test_frame_receipt_skipped_status_rlp_roundtrip() {
747 let fr = FrameReceipt {
749 status: FRAME_RECEIPT_STATUS_SKIPPED,
750 gas_used: 0,
751 logs: vec![],
752 };
753 let encoded = fr.encode_to_vec();
754 let decoded = FrameReceipt::decode(&encoded).unwrap();
755 assert_eq!(fr, decoded);
756 assert_eq!(decoded.status, FRAME_RECEIPT_STATUS_SKIPPED);
757 }
758
759 #[test]
760 fn test_receipt_with_frame_fields_rlp_roundtrip() {
761 let receipt = Receipt {
765 tx_type: TxType::Frame,
766 succeeded: true,
767 cumulative_gas_used: 315000,
768 logs: vec![],
769 payer: Some(Address::from_low_u64_be(0x1234)),
770 frame_receipts: Some(vec![
771 FrameReceipt {
772 status: FRAME_RECEIPT_STATUS_SUCCESS,
773 gas_used: 100000,
774 logs: vec![],
775 },
776 FrameReceipt {
777 status: FRAME_RECEIPT_STATUS_SUCCESS,
778 gas_used: 200000,
779 logs: vec![Log {
780 address: Address::from_low_u64_be(0xbeef),
781 topics: vec![],
782 data: Bytes::from_static(b"frame2"),
783 }],
784 },
785 ]),
786 };
787 let encoded = receipt.encode_to_vec();
788 let decoded = Receipt::decode(&encoded).unwrap();
789 assert_eq!(decoded.tx_type, TxType::Frame);
790 assert_eq!(decoded.cumulative_gas_used, 315000);
791 assert_eq!(decoded.payer, Some(Address::from_low_u64_be(0x1234)));
792 assert!(decoded.succeeded); assert!(decoded.logs.is_empty()); assert_eq!(decoded.frame_receipts, receipt.frame_receipts);
795 }
796
797 #[test]
798 fn test_frame_receipt_succeeded_derived_from_frames() {
799 let receipt = Receipt {
800 tx_type: TxType::Frame,
801 succeeded: true, cumulative_gas_used: 100000,
803 logs: vec![],
804 payer: Some(Address::from_low_u64_be(0x1)),
805 frame_receipts: Some(vec![
806 FrameReceipt {
807 status: FRAME_RECEIPT_STATUS_SUCCESS,
808 gas_used: 50000,
809 logs: vec![],
810 },
811 FrameReceipt {
812 status: FRAME_RECEIPT_STATUS_FAILURE,
813 gas_used: 50000,
814 logs: vec![],
815 },
816 ]),
817 };
818 let encoded = receipt.encode_to_vec();
819 let decoded = Receipt::decode(&encoded).unwrap();
820 assert!(!decoded.succeeded); }
822
823 #[test]
824 fn test_frame_receipt_skipped_disqualifies_succeeded() {
825 let receipt = Receipt {
828 tx_type: TxType::Frame,
829 succeeded: false,
830 cumulative_gas_used: 100000,
831 logs: vec![],
832 payer: Some(Address::from_low_u64_be(0x1)),
833 frame_receipts: Some(vec![
834 FrameReceipt {
835 status: FRAME_RECEIPT_STATUS_SUCCESS,
836 gas_used: 50000,
837 logs: vec![],
838 },
839 FrameReceipt {
840 status: FRAME_RECEIPT_STATUS_SKIPPED,
841 gas_used: 0,
842 logs: vec![],
843 },
844 ]),
845 };
846 let encoded = receipt.encode_to_vec();
847 let decoded = Receipt::decode(&encoded).unwrap();
848 assert!(!decoded.succeeded);
849 }
850
851 #[test]
852 fn frame_receipt_storage_roundtrip_preserves_logs_and_status() {
853 let log = Log {
854 address: Address::from_low_u64_be(1),
855 topics: vec![],
856 data: Bytes::from(vec![1u8]),
857 };
858 let receipt = Receipt {
859 tx_type: TxType::Frame,
860 succeeded: true, cumulative_gas_used: 50_000,
862 logs: vec![log.clone()], payer: Some(Address::from_low_u64_be(2)),
864 frame_receipts: Some(vec![
865 FrameReceipt {
866 status: FRAME_RECEIPT_STATUS_FAILURE,
867 gas_used: 1000,
868 logs: vec![],
869 }, FrameReceipt {
871 status: FRAME_RECEIPT_STATUS_SUCCESS,
872 gas_used: 2000,
873 logs: vec![log],
874 },
875 ]),
876 };
877 let decoded = Receipt::decode_storage(&receipt.encode_storage()).unwrap();
880 assert_eq!(decoded, receipt);
881 }
882
883 #[test]
884 fn nonframe_receipt_storage_roundtrip_unchanged() {
885 let r = Receipt::new(TxType::EIP1559, true, 21000, vec![]);
886 assert_eq!(Receipt::decode_storage(&r.encode_storage()).unwrap(), r);
887 assert_eq!(r.encode_storage(), r.encode_inner());
889 }
890
891 #[test]
892 fn frame_receipt_trie_encoding_is_eip8141_payload() {
893 let receipt = Receipt {
894 tx_type: TxType::Frame,
895 succeeded: true,
896 cumulative_gas_used: 100_000,
897 logs: Vec::new(),
898 payer: Some(Address::from_low_u64_be(0xBEEF)),
899 frame_receipts: Some(vec![FrameReceipt {
900 status: FRAME_RECEIPT_STATUS_SUCCESS,
901 gas_used: 21_000,
902 logs: Vec::new(),
903 }]),
904 };
905 let encoded = receipt.encode_inner_with_bloom(ðrex_crypto::NativeCrypto);
906 assert_eq!(encoded[0], 0x06);
908 let mut expected_payload = Vec::new();
910 Encoder::new(&mut expected_payload)
911 .encode_field(&100_000u64)
912 .encode_field(&Address::from_low_u64_be(0xBEEF))
913 .encode_field(receipt.frame_receipts.as_ref().unwrap())
914 .finish();
915 assert_eq!(&encoded[1..], &expected_payload[..]);
916 }
917
918 #[test]
919 fn decode_inner_with_bloom_roundtrips_nonframe() {
920 let receipt = Receipt {
924 tx_type: TxType::EIP1559,
925 succeeded: true,
926 cumulative_gas_used: 54321,
927 logs: vec![Log {
928 address: Address::from_low_u64_be(0xabcd),
929 topics: vec![h256_from_hex(
930 "e70c0d1060ffbafc84e0e18d028245de3deeb0f41ecbade6562fa657d85ae945",
931 )],
932 data: Bytes::from_static(b"hello"),
933 }],
934 payer: None,
935 frame_receipts: None,
936 };
937 let encoded = receipt.encode_inner_with_bloom(ðrex_crypto::NativeCrypto);
938 let (decoded, rest) = Receipt::decode_inner_with_bloom(&encoded).unwrap();
939 assert!(rest.is_empty());
940 assert_eq!(decoded, receipt);
941 }
942
943 #[test]
944 fn decode_inner_with_bloom_roundtrips_legacy() {
945 let receipt = Receipt {
946 tx_type: TxType::Legacy,
947 succeeded: false,
948 cumulative_gas_used: 100,
949 logs: vec![],
950 payer: None,
951 frame_receipts: None,
952 };
953 let encoded = receipt.encode_inner_with_bloom(ðrex_crypto::NativeCrypto);
954 let (decoded, rest) = Receipt::decode_inner_with_bloom(&encoded).unwrap();
955 assert!(rest.is_empty());
956 assert_eq!(decoded, receipt);
957 }
958
959 #[test]
960 fn decode_inner_with_bloom_roundtrips_frame_preserving_payer_and_frames() {
961 let receipt = Receipt {
964 tx_type: TxType::Frame,
965 succeeded: true,
966 cumulative_gas_used: 315000,
967 logs: vec![],
968 payer: Some(Address::from_low_u64_be(0x1234)),
969 frame_receipts: Some(vec![
970 FrameReceipt {
971 status: FRAME_RECEIPT_STATUS_SUCCESS,
972 gas_used: 100000,
973 logs: vec![],
974 },
975 FrameReceipt {
976 status: FRAME_RECEIPT_STATUS_SUCCESS,
977 gas_used: 200000,
978 logs: vec![Log {
979 address: Address::from_low_u64_be(0xbeef),
980 topics: vec![],
981 data: Bytes::from_static(b"frame2"),
982 }],
983 },
984 ]),
985 };
986 let encoded = receipt.encode_inner_with_bloom(ðrex_crypto::NativeCrypto);
987 let (decoded, rest) = Receipt::decode_inner_with_bloom(&encoded).unwrap();
988 assert!(rest.is_empty());
989 assert_eq!(decoded.tx_type, TxType::Frame);
990 assert_eq!(decoded.cumulative_gas_used, 315000);
991 assert_eq!(decoded.payer, Some(Address::from_low_u64_be(0x1234)));
992 assert_eq!(decoded.frame_receipts, receipt.frame_receipts);
993 assert!(decoded.succeeded); assert!(decoded.logs.is_empty());
995 assert_eq!(decoded, receipt);
997 }
998
999 #[test]
1000 fn decode_inner_with_bloom_frame_zero_payer_maps_to_none() {
1001 let receipt = Receipt {
1002 tx_type: TxType::Frame,
1003 succeeded: true,
1004 cumulative_gas_used: 21000,
1005 logs: vec![],
1006 payer: None,
1007 frame_receipts: Some(vec![FrameReceipt {
1008 status: FRAME_RECEIPT_STATUS_SUCCESS,
1009 gas_used: 21000,
1010 logs: vec![],
1011 }]),
1012 };
1013 let encoded = receipt.encode_inner_with_bloom(ðrex_crypto::NativeCrypto);
1014 let (decoded, _) = Receipt::decode_inner_with_bloom(&encoded).unwrap();
1015 assert_eq!(decoded.payer, None);
1016 assert_eq!(decoded, receipt);
1017 }
1018}