fuel_tx/
receipt.rs

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    // return keyword is reserved
240    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    // TODO: Rewrite the test cases when `Receipt` will have its struct for
778    //  each variant. It will allow to use `Default` trait.
779    #[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    // TODO: Rewrite the test cases when `Receipt` will have its struct for
831    //  each variant. It will allow to use `Default` trait.
832    #[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}