1use crate::Output;
2use alloc::vec::Vec;
3use educe::Educe;
4use fuel_asm::PanicInstruction;
5use fuel_crypto::Hasher;
6use fuel_types::{
7 canonical::{
8 Deserialize,
9 Serialize,
10 },
11 fmt_option_truncated_hex,
12 Address,
13 AssetId,
14 Bytes32,
15 ContractId,
16 MessageId,
17 Nonce,
18 Word,
19};
20
21mod receipt_repr;
22mod script_result;
23
24use crate::input::message::compute_message_id;
25pub use script_result::ScriptExecutionResult;
26
27#[derive(Clone, Educe, serde::Serialize, serde::Deserialize, Deserialize, Serialize)]
28#[educe(Eq, PartialEq, Hash, Debug)]
29pub enum Receipt {
30 Call {
31 id: ContractId,
32 to: ContractId,
33 amount: Word,
34 asset_id: AssetId,
35 gas: Word,
36 param1: Word,
37 param2: Word,
38 pc: Word,
39 is: Word,
40 },
41
42 Return {
43 id: ContractId,
44 val: Word,
45 pc: Word,
46 is: Word,
47 },
48
49 ReturnData {
50 id: ContractId,
51 ptr: Word,
52 len: Word,
53 digest: Bytes32,
54 pc: Word,
55 is: Word,
56 #[educe(Debug(method("fmt_option_truncated_hex::<16>")))]
57 #[educe(PartialEq(ignore))]
58 #[educe(Hash(ignore))]
59 #[canonical(skip)]
60 data: Option<Vec<u8>>,
61 },
62
63 Panic {
64 id: ContractId,
65 reason: PanicInstruction,
66 pc: Word,
67 is: Word,
68 #[educe(PartialEq(ignore))]
69 #[educe(Hash(ignore))]
70 #[canonical(skip)]
71 contract_id: Option<ContractId>,
72 },
73
74 Revert {
75 id: ContractId,
76 ra: Word,
77 pc: Word,
78 is: Word,
79 },
80
81 Log {
82 id: ContractId,
83 ra: Word,
84 rb: Word,
85 rc: Word,
86 rd: Word,
87 pc: Word,
88 is: Word,
89 },
90
91 LogData {
92 id: ContractId,
93 ra: Word,
94 rb: Word,
95 ptr: Word,
96 len: Word,
97 digest: Bytes32,
98 pc: Word,
99 is: Word,
100 #[educe(Debug(method("fmt_option_truncated_hex::<16>")))]
101 #[educe(PartialEq(ignore))]
102 #[educe(Hash(ignore))]
103 #[canonical(skip)]
104 data: Option<Vec<u8>>,
105 },
106
107 Transfer {
108 id: ContractId,
109 to: ContractId,
110 amount: Word,
111 asset_id: AssetId,
112 pc: Word,
113 is: Word,
114 },
115
116 TransferOut {
117 id: ContractId,
118 to: Address,
119 amount: Word,
120 asset_id: AssetId,
121 pc: Word,
122 is: Word,
123 },
124
125 ScriptResult {
126 result: ScriptExecutionResult,
127 gas_used: Word,
128 },
129
130 MessageOut {
131 sender: Address,
132 recipient: Address,
133 amount: Word,
134 nonce: Nonce,
135 len: Word,
136 digest: Bytes32,
137 #[educe(Debug(method("fmt_option_truncated_hex::<16>")))]
138 #[educe(PartialEq(ignore))]
139 #[educe(Hash(ignore))]
140 #[canonical(skip)]
141 data: Option<Vec<u8>>,
142 },
143 Mint {
144 sub_id: Bytes32,
145 contract_id: ContractId,
146 val: Word,
147 pc: Word,
148 is: Word,
149 },
150 Burn {
151 sub_id: Bytes32,
152 contract_id: ContractId,
153 val: Word,
154 pc: Word,
155 is: Word,
156 },
157}
158
159impl core::fmt::Display for Receipt {
160 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
161 match self {
162 Receipt::Call { id, .. } => write!(f, "Call(id={})", id),
163 Receipt::Return { id, .. } => write!(f, "Return(id={})", id),
164 Receipt::ReturnData { id, .. } => write!(f, "ReturnData(id={})", id),
165 Receipt::Panic { id, reason, .. } => {
166 write!(f, "Panic(id={}, reason={:?})", id, reason.reason())
167 }
168 Receipt::Revert { id, .. } => write!(f, "Revert(id={})", id),
169 Receipt::Log { id, .. } => write!(f, "Log(id={})", id),
170 Receipt::LogData { id, .. } => write!(f, "LogData(id={})", id),
171 Receipt::Transfer { id, .. } => write!(f, "Transfer(id={})", id),
172 Receipt::TransferOut { id, .. } => write!(f, "TransferOut(id={})", id),
173 Receipt::ScriptResult { result, gas_used } => {
174 write!(
175 f,
176 "ScriptResult(result={:?}, gas_used={})",
177 result, gas_used
178 )
179 }
180 Receipt::MessageOut {
181 sender,
182 recipient,
183 amount,
184 ..
185 } => write!(
186 f,
187 "MessageOut(sender={}, recipient={}, amount={})",
188 sender, recipient, amount
189 ),
190 Receipt::Mint {
191 sub_id,
192 contract_id,
193 val,
194 ..
195 } => write!(
196 f,
197 "Mint(sub_id={}, contract_id={}, val={})",
198 sub_id, contract_id, val
199 ),
200 Receipt::Burn {
201 sub_id,
202 contract_id,
203 val,
204 ..
205 } => write!(
206 f,
207 "Burn(sub_id={}, contract_id={}, val={})",
208 sub_id, contract_id, val
209 ),
210 }
211 }
212}
213
214impl Receipt {
215 pub const fn call(
216 id: ContractId,
217 to: ContractId,
218 amount: Word,
219 asset_id: AssetId,
220 gas: Word,
221 param1: Word,
222 param2: Word,
223 pc: Word,
224 is: Word,
225 ) -> Self {
226 Self::Call {
227 id,
228 to,
229 amount,
230 asset_id,
231 gas,
232 param1,
233 param2,
234 pc,
235 is,
236 }
237 }
238
239 pub const fn ret(id: ContractId, val: Word, pc: Word, is: Word) -> Self {
241 Self::Return { id, val, pc, is }
242 }
243
244 pub fn return_data(
245 id: ContractId,
246 ptr: Word,
247 pc: Word,
248 is: Word,
249 data: Vec<u8>,
250 ) -> Self {
251 let digest = Hasher::hash(&data);
252 Self::return_data_with_len(
253 id,
254 ptr,
255 data.len() as Word,
256 digest,
257 pc,
258 is,
259 Some(data),
260 )
261 }
262
263 pub const fn return_data_with_len(
264 id: ContractId,
265 ptr: Word,
266 len: Word,
267 digest: Bytes32,
268 pc: Word,
269 is: Word,
270 data: Option<Vec<u8>>,
271 ) -> Self {
272 Self::ReturnData {
273 id,
274 ptr,
275 len,
276 digest,
277 pc,
278 is,
279 data,
280 }
281 }
282
283 pub const fn panic(
284 id: ContractId,
285 reason: PanicInstruction,
286 pc: Word,
287 is: Word,
288 ) -> Self {
289 Self::Panic {
290 id,
291 reason,
292 pc,
293 is,
294 contract_id: None,
295 }
296 }
297
298 pub fn with_panic_contract_id(mut self, _contract_id: Option<ContractId>) -> Self {
299 if let Receipt::Panic {
300 ref mut contract_id,
301 ..
302 } = self
303 {
304 *contract_id = _contract_id;
305 }
306 self
307 }
308
309 pub const fn revert(id: ContractId, ra: Word, pc: Word, is: Word) -> Self {
310 Self::Revert { id, ra, pc, is }
311 }
312
313 pub const fn log(
314 id: ContractId,
315 ra: Word,
316 rb: Word,
317 rc: Word,
318 rd: Word,
319 pc: Word,
320 is: Word,
321 ) -> Self {
322 Self::Log {
323 id,
324 ra,
325 rb,
326 rc,
327 rd,
328 pc,
329 is,
330 }
331 }
332
333 pub fn log_data(
334 id: ContractId,
335 ra: Word,
336 rb: Word,
337 ptr: Word,
338 pc: Word,
339 is: Word,
340 data: Vec<u8>,
341 ) -> Self {
342 let digest = Hasher::hash(&data);
343 Self::log_data_with_len(
344 id,
345 ra,
346 rb,
347 ptr,
348 data.len() as Word,
349 digest,
350 pc,
351 is,
352 Some(data),
353 )
354 }
355
356 pub const fn log_data_with_len(
357 id: ContractId,
358 ra: Word,
359 rb: Word,
360 ptr: Word,
361 len: Word,
362 digest: Bytes32,
363 pc: Word,
364 is: Word,
365 data: Option<Vec<u8>>,
366 ) -> Self {
367 Self::LogData {
368 id,
369 ra,
370 rb,
371 ptr,
372 len,
373 digest,
374 pc,
375 is,
376 data,
377 }
378 }
379
380 pub const fn transfer(
381 id: ContractId,
382 to: ContractId,
383 amount: Word,
384 asset_id: AssetId,
385 pc: Word,
386 is: Word,
387 ) -> Self {
388 Self::Transfer {
389 id,
390 to,
391 amount,
392 asset_id,
393 pc,
394 is,
395 }
396 }
397
398 pub const fn transfer_out(
399 id: ContractId,
400 to: Address,
401 amount: Word,
402 asset_id: AssetId,
403 pc: Word,
404 is: Word,
405 ) -> Self {
406 Self::TransferOut {
407 id,
408 to,
409 amount,
410 asset_id,
411 pc,
412 is,
413 }
414 }
415
416 pub const fn script_result(result: ScriptExecutionResult, gas_used: Word) -> Self {
417 Self::ScriptResult { result, gas_used }
418 }
419
420 pub fn message_out(
421 txid: &Bytes32,
422 idx: Word,
423 sender: Address,
424 recipient: Address,
425 amount: Word,
426 data: Vec<u8>,
427 ) -> Self {
428 let nonce = Output::message_nonce(txid, idx);
429 let digest = Output::message_digest(&data);
430
431 Self::message_out_with_len(
432 sender,
433 recipient,
434 amount,
435 nonce,
436 data.len() as Word,
437 digest,
438 Some(data),
439 )
440 }
441
442 pub const fn message_out_with_len(
443 sender: Address,
444 recipient: Address,
445 amount: Word,
446 nonce: Nonce,
447 len: Word,
448 digest: Bytes32,
449 data: Option<Vec<u8>>,
450 ) -> Self {
451 Self::MessageOut {
452 sender,
453 recipient,
454 amount,
455 nonce,
456 len,
457 digest,
458 data,
459 }
460 }
461
462 pub fn mint(
463 sub_id: Bytes32,
464 contract_id: ContractId,
465 val: Word,
466 pc: Word,
467 is: Word,
468 ) -> Self {
469 Self::Mint {
470 sub_id,
471 contract_id,
472 val,
473 pc,
474 is,
475 }
476 }
477
478 pub fn burn(
479 sub_id: Bytes32,
480 contract_id: ContractId,
481 val: Word,
482 pc: Word,
483 is: Word,
484 ) -> Self {
485 Self::Burn {
486 sub_id,
487 contract_id,
488 val,
489 pc,
490 is,
491 }
492 }
493
494 #[inline(always)]
495 pub fn id(&self) -> Option<&ContractId> {
496 trim_contract_id(match self {
497 Self::Call { id, .. } => Some(id),
498 Self::Return { id, .. } => Some(id),
499 Self::ReturnData { id, .. } => Some(id),
500 Self::Panic { id, .. } => Some(id),
501 Self::Revert { id, .. } => Some(id),
502 Self::Log { id, .. } => Some(id),
503 Self::LogData { id, .. } => Some(id),
504 Self::Transfer { id, .. } => Some(id),
505 Self::TransferOut { id, .. } => Some(id),
506 Self::ScriptResult { .. } => None,
507 Self::MessageOut { .. } => None,
508 Self::Mint { contract_id, .. } => Some(contract_id),
509 Self::Burn { contract_id, .. } => Some(contract_id),
510 })
511 }
512
513 pub const fn sub_id(&self) -> Option<&Bytes32> {
514 match self {
515 Self::Mint { sub_id, .. } => Some(sub_id),
516 Self::Burn { sub_id, .. } => Some(sub_id),
517 _ => None,
518 }
519 }
520
521 pub const fn pc(&self) -> Option<Word> {
522 match self {
523 Self::Call { pc, .. } => Some(*pc),
524 Self::Return { pc, .. } => Some(*pc),
525 Self::ReturnData { pc, .. } => Some(*pc),
526 Self::Panic { pc, .. } => Some(*pc),
527 Self::Revert { pc, .. } => Some(*pc),
528 Self::Log { pc, .. } => Some(*pc),
529 Self::LogData { pc, .. } => Some(*pc),
530 Self::Transfer { pc, .. } => Some(*pc),
531 Self::TransferOut { pc, .. } => Some(*pc),
532 Self::ScriptResult { .. } => None,
533 Self::MessageOut { .. } => None,
534 Self::Mint { pc, .. } => Some(*pc),
535 Self::Burn { pc, .. } => Some(*pc),
536 }
537 }
538
539 pub const fn is(&self) -> Option<Word> {
540 match self {
541 Self::Call { is, .. } => Some(*is),
542 Self::Return { is, .. } => Some(*is),
543 Self::ReturnData { is, .. } => Some(*is),
544 Self::Panic { is, .. } => Some(*is),
545 Self::Revert { is, .. } => Some(*is),
546 Self::Log { is, .. } => Some(*is),
547 Self::LogData { is, .. } => Some(*is),
548 Self::Transfer { is, .. } => Some(*is),
549 Self::TransferOut { is, .. } => Some(*is),
550 Self::ScriptResult { .. } => None,
551 Self::MessageOut { .. } => None,
552 Self::Mint { is, .. } => Some(*is),
553 Self::Burn { is, .. } => Some(*is),
554 }
555 }
556
557 #[inline(always)]
558 pub fn to(&self) -> Option<&ContractId> {
559 trim_contract_id(match self {
560 Self::Call { to, .. } => Some(to),
561 Self::Transfer { to, .. } => Some(to),
562 _ => None,
563 })
564 }
565
566 pub const fn to_address(&self) -> Option<&Address> {
567 match self {
568 Self::TransferOut { to, .. } => Some(to),
569 _ => None,
570 }
571 }
572
573 pub const fn amount(&self) -> Option<Word> {
574 match self {
575 Self::Call { amount, .. } => Some(*amount),
576 Self::Transfer { amount, .. } => Some(*amount),
577 Self::TransferOut { amount, .. } => Some(*amount),
578 Self::MessageOut { amount, .. } => Some(*amount),
579 _ => None,
580 }
581 }
582
583 pub const fn asset_id(&self) -> Option<&AssetId> {
584 match self {
585 Self::Call { asset_id, .. } => Some(asset_id),
586 Self::Transfer { asset_id, .. } => Some(asset_id),
587 Self::TransferOut { asset_id, .. } => Some(asset_id),
588 _ => None,
589 }
590 }
591
592 pub const fn gas(&self) -> Option<Word> {
593 match self {
594 Self::Call { gas, .. } => Some(*gas),
595 _ => None,
596 }
597 }
598
599 pub const fn param1(&self) -> Option<Word> {
600 match self {
601 Self::Call { param1, .. } => Some(*param1),
602 _ => None,
603 }
604 }
605
606 pub const fn param2(&self) -> Option<Word> {
607 match self {
608 Self::Call { param2, .. } => Some(*param2),
609 _ => None,
610 }
611 }
612
613 pub const fn val(&self) -> Option<Word> {
614 match self {
615 Self::Return { val, .. } => Some(*val),
616 Self::Mint { val, .. } => Some(*val),
617 Self::Burn { val, .. } => Some(*val),
618 _ => None,
619 }
620 }
621
622 pub const fn ptr(&self) -> Option<Word> {
623 match self {
624 Self::ReturnData { ptr, .. } => Some(*ptr),
625 Self::LogData { ptr, .. } => Some(*ptr),
626 _ => None,
627 }
628 }
629
630 pub const fn len(&self) -> Option<Word> {
631 match self {
632 Self::ReturnData { len, .. } => Some(*len),
633 Self::LogData { len, .. } => Some(*len),
634 Self::MessageOut { len, .. } => Some(*len),
635 _ => None,
636 }
637 }
638
639 pub const fn is_empty(&self) -> Option<bool> {
640 match self.len() {
641 Some(0) => Some(true),
642 Some(_) => Some(false),
643 None => None,
644 }
645 }
646
647 pub const fn digest(&self) -> Option<&Bytes32> {
648 match self {
649 Self::ReturnData { digest, .. } => Some(digest),
650 Self::LogData { digest, .. } => Some(digest),
651 Self::MessageOut { digest, .. } => Some(digest),
652 _ => None,
653 }
654 }
655
656 pub fn data(&self) -> Option<&[u8]> {
657 match self {
658 Self::ReturnData { data, .. } => data.as_ref().map(|data| data.as_slice()),
659 Self::LogData { data, .. } => data.as_ref().map(|data| data.as_slice()),
660 Self::MessageOut { data, .. } => data.as_ref().map(|data| data.as_slice()),
661 _ => None,
662 }
663 }
664
665 pub const fn reason(&self) -> Option<PanicInstruction> {
666 match self {
667 Self::Panic { reason, .. } => Some(*reason),
668 _ => None,
669 }
670 }
671
672 pub const fn ra(&self) -> Option<Word> {
673 match self {
674 Self::Revert { ra, .. } => Some(*ra),
675 Self::Log { ra, .. } => Some(*ra),
676 Self::LogData { ra, .. } => Some(*ra),
677 _ => None,
678 }
679 }
680
681 pub const fn rb(&self) -> Option<Word> {
682 match self {
683 Self::Log { rb, .. } => Some(*rb),
684 Self::LogData { rb, .. } => Some(*rb),
685 _ => None,
686 }
687 }
688
689 pub const fn rc(&self) -> Option<Word> {
690 match self {
691 Self::Log { rc, .. } => Some(*rc),
692 _ => None,
693 }
694 }
695
696 pub const fn rd(&self) -> Option<Word> {
697 match self {
698 Self::Log { rd, .. } => Some(*rd),
699 _ => None,
700 }
701 }
702
703 pub const fn result(&self) -> Option<&ScriptExecutionResult> {
704 match self {
705 Self::ScriptResult { result, .. } => Some(result),
706 _ => None,
707 }
708 }
709
710 pub const fn gas_used(&self) -> Option<Word> {
711 match self {
712 Self::ScriptResult { gas_used, .. } => Some(*gas_used),
713 _ => None,
714 }
715 }
716
717 pub fn message_id(&self) -> Option<MessageId> {
718 match self {
719 Self::MessageOut {
720 sender,
721 recipient,
722 amount,
723 nonce,
724 data,
725 ..
726 } => data.as_ref().map(|data| {
727 compute_message_id(sender, recipient, nonce, *amount, data.as_slice())
728 }),
729 _ => None,
730 }
731 }
732
733 pub const fn sender(&self) -> Option<&Address> {
734 match self {
735 Self::MessageOut { sender, .. } => Some(sender),
736 _ => None,
737 }
738 }
739
740 pub const fn recipient(&self) -> Option<&Address> {
741 match self {
742 Self::MessageOut { recipient, .. } => Some(recipient),
743 _ => None,
744 }
745 }
746
747 pub const fn nonce(&self) -> Option<&Nonce> {
748 match self {
749 Self::MessageOut { nonce, .. } => Some(nonce),
750 _ => None,
751 }
752 }
753
754 pub const fn contract_id(&self) -> Option<&ContractId> {
755 match self {
756 Self::Panic { contract_id, .. } => contract_id.as_ref(),
757 _ => None,
758 }
759 }
760}
761
762fn trim_contract_id(id: Option<&ContractId>) -> Option<&ContractId> {
763 id.and_then(|id| {
764 if id != &ContractId::zeroed() {
765 Some(id)
766 } else {
767 None
768 }
769 })
770}
771
772#[cfg(test)]
773mod tests {
774 use crate::Receipt;
775 use fuel_types::ContractId;
776
777 #[rstest::rstest]
780 #[case(
781 Receipt::Call {
782 id: ContractId::from([1; 32]),
783 to: Default::default(),
784 amount: 0,
785 asset_id: Default::default(),
786 gas: 0,
787 param1: 0,
788 param2: 0,
789 pc: 0,
790 is: 0,
791 },
792 Some(ContractId::from([1; 32]))
793 )]
794 #[case(
795 Receipt::Call {
796 id: ContractId::from([0; 32]),
797 to: Default::default(),
798 amount: 0,
799 asset_id: Default::default(),
800 gas: 0,
801 param1: 0,
802 param2: 0,
803 pc: 0,
804 is: 0,
805 },
806 None
807 )]
808 #[case(
809 Receipt::Return {
810 id: ContractId::from([2; 32]),
811 val: 0,
812 pc: 0,
813 is: 0,
814 },
815 Some(ContractId::from([2; 32]))
816 )]
817 #[case(
818 Receipt::Return {
819 id: ContractId::from([0; 32]),
820 val: 0,
821 pc: 0,
822 is: 0,
823 },
824 None
825 )]
826 fn receipt_id(#[case] receipt: Receipt, #[case] expected_id: Option<ContractId>) {
827 assert_eq!(receipt.id(), expected_id.as_ref());
828 }
829
830 #[rstest::rstest]
833 #[case(
834 Receipt::Call {
835 id: Default::default(),
836 to: ContractId::from([1; 32]),
837 amount: 0,
838 asset_id: Default::default(),
839 gas: 0,
840 param1: 0,
841 param2: 0,
842 pc: 0,
843 is: 0,
844 },
845 Some(ContractId::from([1; 32]))
846 )]
847 #[case(
848 Receipt::Call {
849 id: Default::default(),
850 to: ContractId::from([0; 32]),
851 amount: 0,
852 asset_id: Default::default(),
853 gas: 0,
854 param1: 0,
855 param2: 0,
856 pc: 0,
857 is: 0,
858 },
859 None
860 )]
861 #[case(
862 Receipt::Return {
863 id: Default::default(),
864 val: 0,
865 pc: 0,
866 is: 0,
867 },
868 None
869 )]
870 fn receipt_to(#[case] receipt: Receipt, #[case] expected_to: Option<ContractId>) {
871 assert_eq!(receipt.to(), expected_to.as_ref());
872 }
873}