1#![allow(non_upper_case_globals)]
9
10use fuel_tx::{
11 Create,
12 Mint,
13 Script,
14 Transaction,
15 ValidityError,
16 field::Expiration,
17};
18use fuel_types::{
19 BlockHeight,
20 ChainId,
21};
22
23use alloc::{
24 boxed::Box,
25 vec::Vec,
26};
27use core::{
28 borrow::Borrow,
29 fmt::Debug,
30 future::Future,
31};
32use fuel_tx::{
33 ConsensusParameters,
34 field::MaxFeeLimit,
35};
36
37mod balances;
38#[cfg(feature = "test-helpers")]
39pub mod builder;
40pub mod types;
41
42pub use types::*;
43
44use crate::{
45 error::PredicateVerificationFailed,
46 interpreter::{
47 EcalHandler,
48 Memory,
49 MemoryInstance,
50 NotSupportedEcal,
51 },
52 pool::VmMemoryPool,
53 prelude::*,
54 storage::predicate::{
55 EmptyStorage,
56 PredicateStorageProvider,
57 PredicateStorageRequirements,
58 },
59};
60
61bitflags::bitflags! {
62 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
64 pub struct Checks: u32 {
65 const Basic = 0b00000001;
69 const Signatures = 0b00000010;
71 const Predicates = 0b00000100;
73 }
74}
75
76impl core::fmt::Display for Checks {
77 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
78 write!(f, "{:032b}", self.bits())
79 }
80}
81
82#[derive(Debug, Clone, Eq, PartialEq, Hash)]
97pub struct Checked<Tx: IntoChecked> {
98 transaction: Tx,
99 metadata: Tx::Metadata,
100 checks_bitmask: Checks,
101}
102
103impl<Tx: IntoChecked> Checked<Tx> {
104 fn new(transaction: Tx, metadata: Tx::Metadata, checks_bitmask: Checks) -> Self {
105 Checked {
106 transaction,
107 metadata,
108 checks_bitmask,
109 }
110 }
111
112 pub(crate) fn basic(transaction: Tx, metadata: Tx::Metadata) -> Self {
113 Checked::new(transaction, metadata, Checks::Basic)
114 }
115
116 pub fn transaction(&self) -> &Tx {
118 &self.transaction
119 }
120
121 pub fn metadata(&self) -> &Tx::Metadata {
123 &self.metadata
124 }
125
126 pub fn checks(&self) -> &Checks {
128 &self.checks_bitmask
129 }
130
131 pub fn check_signatures(mut self, chain_id: &ChainId) -> Result<Self, CheckError> {
133 if !self.checks_bitmask.contains(Checks::Signatures) {
134 self.transaction.check_signatures(chain_id)?;
135 self.checks_bitmask.insert(Checks::Signatures);
136 }
137 Ok(self)
138 }
139}
140
141#[derive(Debug, Clone, Eq, PartialEq, Hash)]
143pub struct Ready<Tx: IntoChecked> {
144 gas_price: Word,
145 transaction: Tx,
146 metadata: Tx::Metadata,
147 checks_bitmask: Checks,
148}
149
150impl<Tx: IntoChecked> Ready<Tx> {
151 pub fn decompose(self) -> (Word, Checked<Tx>) {
153 let Ready {
154 gas_price,
155 transaction,
156 metadata,
157 checks_bitmask,
158 } = self;
159 let checked = Checked::new(transaction, metadata, checks_bitmask);
160 (gas_price, checked)
161 }
162
163 pub fn gas_price(&self) -> Word {
165 self.gas_price
166 }
167}
168
169#[cfg(feature = "test-helpers")]
170impl<Tx: IntoChecked> Checked<Tx> {
171 pub fn test_into_ready(self) -> Ready<Tx> {
173 let Checked {
174 transaction,
175 metadata,
176 checks_bitmask,
177 } = self;
178 Ready {
179 gas_price: 0,
180 transaction,
181 metadata,
182 checks_bitmask,
183 }
184 }
185}
186
187impl<Tx: IntoChecked + Chargeable> Checked<Tx> {
188 pub fn into_ready(
190 self,
191 gas_price: Word,
192 gas_costs: &GasCosts,
193 fee_parameters: &FeeParameters,
194 block_height: Option<BlockHeight>,
195 ) -> Result<Ready<Tx>, CheckError> {
196 let Checked {
197 transaction,
198 metadata,
199 checks_bitmask,
200 } = self;
201 let fee = TransactionFee::checked_from_tx(
202 gas_costs,
203 fee_parameters,
204 &transaction,
205 gas_price,
206 )
207 .ok_or(CheckError::Validity(ValidityError::BalanceOverflow))?;
208
209 let max_fee_from_policies = transaction.max_fee_limit();
210 let max_fee_from_gas_price = fee.max_fee();
211
212 if let Some(block_height) = block_height
213 && block_height > transaction.expiration()
214 {
215 return Err(CheckError::Validity(ValidityError::TransactionExpiration));
216 }
217
218 if max_fee_from_gas_price > max_fee_from_policies {
219 Err(CheckError::InsufficientMaxFee {
220 max_fee_from_policies,
221 max_fee_from_gas_price,
222 })
223 } else {
224 Ok(Ready {
225 gas_price,
226 transaction,
227 metadata,
228 checks_bitmask,
229 })
230 }
231 }
232}
233
234impl<Tx: IntoChecked + UniqueIdentifier> Checked<Tx> {
235 pub fn id(&self) -> TxId {
237 self.transaction
238 .cached_id()
239 .expect("Transaction metadata should be computed for checked transactions")
240 }
241}
242
243#[cfg(feature = "test-helpers")]
244impl<Tx: IntoChecked + Default> Default for Checked<Tx>
245where
246 Checked<Tx>: CheckPredicates,
247{
248 fn default() -> Self {
249 Tx::default()
250 .into_checked(Default::default(), &ConsensusParameters::standard())
251 .expect("default tx should produce a valid fully checked transaction")
252 }
253}
254
255impl<Tx: IntoChecked> From<Checked<Tx>> for (Tx, Tx::Metadata) {
256 fn from(checked: Checked<Tx>) -> Self {
257 let Checked {
258 transaction,
259 metadata,
260 ..
261 } = checked;
262
263 (transaction, metadata)
264 }
265}
266
267impl<Tx: IntoChecked> AsRef<Tx> for Checked<Tx> {
268 fn as_ref(&self) -> &Tx {
269 &self.transaction
270 }
271}
272
273#[cfg(feature = "test-helpers")]
274impl<Tx: IntoChecked> AsMut<Tx> for Checked<Tx> {
275 fn as_mut(&mut self) -> &mut Tx {
276 &mut self.transaction
277 }
278}
279
280impl<Tx: IntoChecked> Borrow<Tx> for Checked<Tx> {
281 fn borrow(&self) -> &Tx {
282 self.transaction()
283 }
284}
285
286#[derive(Debug, Clone, PartialEq)]
288#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
289pub enum CheckError {
290 Validity(ValidityError),
292 PredicateVerificationFailed(PredicateVerificationFailed),
294 InsufficientMaxFee {
297 max_fee_from_policies: Word,
299 max_fee_from_gas_price: Word,
301 },
302}
303
304pub trait IntoChecked: FormatValidityChecks + Sized {
306 type Metadata: Sized;
308
309 fn into_checked(
311 self,
312 block_height: BlockHeight,
313 consensus_params: &ConsensusParameters,
314 ) -> Result<Checked<Self>, CheckError>
315 where
316 Checked<Self>: CheckPredicates,
317 {
318 self.into_checked_reusable_memory(
319 block_height,
320 consensus_params,
321 MemoryInstance::new(),
322 &EmptyStorage,
323 )
324 }
325
326 fn into_checked_reusable_memory(
329 self,
330 block_height: BlockHeight,
331 consensus_params: &ConsensusParameters,
332 memory: impl Memory,
333 storage: &impl PredicateStorageRequirements,
334 ) -> Result<Checked<Self>, CheckError>
335 where
336 Checked<Self>: CheckPredicates,
337 {
338 self.into_checked_reusable_memory_ecal(
339 block_height,
340 consensus_params,
341 memory,
342 storage,
343 NotSupportedEcal,
344 )
345 }
346
347 fn into_checked_ecal<Ecal>(
349 self,
350 block_height: BlockHeight,
351 consensus_params: &ConsensusParameters,
352 ecal_handler: Ecal,
353 ) -> Result<Checked<Self>, CheckError>
354 where
355 Checked<Self>: CheckPredicates,
356 Ecal: EcalHandler + Send + 'static,
357 {
358 self.into_checked_reusable_memory_ecal(
359 block_height,
360 consensus_params,
361 MemoryInstance::new(),
362 &EmptyStorage,
363 ecal_handler,
364 )
365 }
366
367 fn into_checked_reusable_memory_ecal<Ecal>(
370 self,
371 block_height: BlockHeight,
372 consensus_params: &ConsensusParameters,
373 memory: impl Memory,
374 storage: &impl PredicateStorageRequirements,
375 ecal_handler: Ecal,
376 ) -> Result<Checked<Self>, CheckError>
377 where
378 Checked<Self>: CheckPredicates,
379 Ecal: EcalHandler + Send + 'static,
380 {
381 let check_predicate_params = consensus_params.into();
382 self.into_checked_basic(block_height, consensus_params)?
383 .check_signatures(&consensus_params.chain_id())?
384 .check_predicates(&check_predicate_params, memory, storage, ecal_handler)
385 }
386
387 fn into_checked_basic(
389 self,
390 block_height: BlockHeight,
391 consensus_params: &ConsensusParameters,
392 ) -> Result<Checked<Self>, CheckError>;
393}
394
395#[derive(Debug, Clone)]
397pub struct CheckPredicateParams {
398 pub gas_costs: GasCosts,
400 pub chain_id: ChainId,
402 pub max_gas_per_predicate: u64,
404 pub max_gas_per_tx: u64,
406 pub max_inputs: u16,
408 pub contract_max_size: u64,
410 pub max_message_data_length: u64,
412 pub max_storage_slot_length: u64,
414 pub tx_offset: usize,
416 pub fee_params: FeeParameters,
418 pub base_asset_id: AssetId,
420}
421
422#[cfg(feature = "test-helpers")]
423impl Default for CheckPredicateParams {
424 fn default() -> Self {
425 CheckPredicateParams::from(&ConsensusParameters::standard())
426 }
427}
428
429impl From<ConsensusParameters> for CheckPredicateParams {
430 fn from(value: ConsensusParameters) -> Self {
431 CheckPredicateParams::from(&value)
432 }
433}
434
435impl From<&ConsensusParameters> for CheckPredicateParams {
436 fn from(value: &ConsensusParameters) -> Self {
437 CheckPredicateParams {
438 gas_costs: value.gas_costs().clone(),
439 chain_id: value.chain_id(),
440 max_gas_per_predicate: value.predicate_params().max_gas_per_predicate(),
441 max_gas_per_tx: value.tx_params().max_gas_per_tx(),
442 max_inputs: value.tx_params().max_inputs(),
443 contract_max_size: value.contract_params().contract_max_size(),
444 max_message_data_length: value.predicate_params().max_message_data_length(),
445 max_storage_slot_length: value.script_params().max_storage_slot_length(),
446 tx_offset: value.tx_params().tx_offset(),
447 fee_params: *(value.fee_params()),
448 base_asset_id: *value.base_asset_id(),
449 }
450 }
451}
452
453#[async_trait::async_trait]
455pub trait CheckPredicates: Sized {
456 fn check_predicates(
458 self,
459 params: &CheckPredicateParams,
460 memory: impl Memory,
461 storage: &impl PredicateStorageRequirements,
462 ecal_handler: impl EcalHandler,
463 ) -> Result<Self, CheckError>;
464
465 async fn check_predicates_async<
467 Ecal: EcalHandler + Send + 'static,
468 E: ParallelExecutor,
469 >(
470 self,
471 params: &CheckPredicateParams,
472 pool: &impl VmMemoryPool,
473 storage: &impl PredicateStorageProvider,
474 ecal_handler: Ecal,
475 ) -> Result<Self, CheckError>;
476}
477
478#[async_trait::async_trait]
480pub trait EstimatePredicates: Sized {
481 fn estimate_predicates(
483 &mut self,
484 params: &CheckPredicateParams,
485 memory: impl Memory,
486 storage: &impl PredicateStorageRequirements,
487 ) -> Result<(), CheckError> {
488 Self::estimate_predicates_ecal(self, params, memory, storage, NotSupportedEcal)
489 }
490
491 async fn estimate_predicates_async<E: ParallelExecutor>(
493 &mut self,
494 params: &CheckPredicateParams,
495 pool: &impl VmMemoryPool,
496 storage: &impl PredicateStorageProvider,
497 ) -> Result<(), CheckError> {
498 Self::estimate_predicates_async_ecal::<NotSupportedEcal, E>(
499 self,
500 params,
501 pool,
502 storage,
503 NotSupportedEcal,
504 )
505 .await
506 }
507
508 fn estimate_predicates_ecal(
510 &mut self,
511 params: &CheckPredicateParams,
512 memory: impl Memory,
513 storage: &impl PredicateStorageRequirements,
514 ecal_handler: impl EcalHandler,
515 ) -> Result<(), CheckError>;
516
517 async fn estimate_predicates_async_ecal<
519 Ecal: EcalHandler + Send + 'static,
520 E: ParallelExecutor,
521 >(
522 &mut self,
523 params: &CheckPredicateParams,
524 pool: &impl VmMemoryPool,
525 storage: &impl PredicateStorageProvider,
526 ecal_handler: Ecal,
527 ) -> Result<(), CheckError>;
528}
529
530#[async_trait::async_trait]
532pub trait ParallelExecutor {
533 type Task: Future + Send + 'static;
535
536 fn create_task<F>(func: F) -> Self::Task
538 where
539 F: FnOnce() -> (usize, Result<Word, PredicateVerificationFailed>)
540 + Send
541 + 'static;
542
543 async fn execute_tasks(
545 futures: Vec<Self::Task>,
546 ) -> Vec<(usize, Result<Word, PredicateVerificationFailed>)>;
547}
548
549#[async_trait::async_trait]
550impl<Tx> CheckPredicates for Checked<Tx>
551where
552 Tx: ExecutableTransaction + Send + Sync + 'static,
553 <Tx as IntoChecked>::Metadata: crate::interpreter::CheckedMetadata + Send + Sync,
554{
555 fn check_predicates(
556 mut self,
557 params: &CheckPredicateParams,
558 memory: impl Memory,
559 storage: &impl PredicateStorageRequirements,
560 ecal_handler: impl EcalHandler,
561 ) -> Result<Self, CheckError> {
562 if !self.checks_bitmask.contains(Checks::Predicates) {
563 predicates::check_predicates(&self, params, memory, storage, ecal_handler)?;
564 self.checks_bitmask.insert(Checks::Predicates);
565 }
566 Ok(self)
567 }
568
569 async fn check_predicates_async<Ecal, E>(
570 mut self,
571 params: &CheckPredicateParams,
572 pool: &impl VmMemoryPool,
573 storage: &impl PredicateStorageProvider,
574 ecal_handler: Ecal,
575 ) -> Result<Self, CheckError>
576 where
577 Ecal: EcalHandler + Send + 'static,
578 E: ParallelExecutor,
579 {
580 if !self.checks_bitmask.contains(Checks::Predicates) {
581 predicates::check_predicates_async::<Tx, Ecal, E>(
582 &self,
583 params,
584 pool,
585 storage,
586 ecal_handler,
587 )
588 .await?;
589
590 self.checks_bitmask.insert(Checks::Predicates);
591
592 Ok(self)
593 } else {
594 Ok(self)
595 }
596 }
597}
598
599#[async_trait::async_trait]
600impl<Tx: ExecutableTransaction + Send + Sync + 'static> EstimatePredicates for Tx {
601 fn estimate_predicates_ecal(
602 &mut self,
603 params: &CheckPredicateParams,
604 memory: impl Memory,
605 storage: &impl PredicateStorageRequirements,
606 ecal_handler: impl EcalHandler,
607 ) -> Result<(), CheckError> {
608 predicates::estimate_predicates::<Self>(
609 self,
610 params,
611 memory,
612 storage,
613 ecal_handler,
614 )?;
615 Ok(())
616 }
617
618 async fn estimate_predicates_async_ecal<Ecal, E>(
619 &mut self,
620 params: &CheckPredicateParams,
621 pool: &impl VmMemoryPool,
622 storage: &impl PredicateStorageProvider,
623 ecal_handler: Ecal,
624 ) -> Result<(), CheckError>
625 where
626 E: ParallelExecutor,
627 Ecal: EcalHandler + Send + 'static,
628 {
629 predicates::estimate_predicates_async::<Self, Ecal, E>(
630 self,
631 params,
632 pool,
633 storage,
634 ecal_handler,
635 )
636 .await?;
637
638 Ok(())
639 }
640}
641
642#[async_trait::async_trait]
643impl EstimatePredicates for Transaction {
644 fn estimate_predicates_ecal(
645 &mut self,
646 params: &CheckPredicateParams,
647 memory: impl Memory,
648 storage: &impl PredicateStorageRequirements,
649 ecal_handler: impl EcalHandler,
650 ) -> Result<(), CheckError> {
651 match self {
652 Self::Script(tx) => {
653 tx.estimate_predicates_ecal(params, memory, storage, ecal_handler)
654 }
655 Self::Create(tx) => {
656 tx.estimate_predicates_ecal(params, memory, storage, ecal_handler)
657 }
658 Self::Mint(_) => Ok(()),
659 Self::Upgrade(tx) => {
660 tx.estimate_predicates_ecal(params, memory, storage, ecal_handler)
661 }
662 Self::Upload(tx) => {
663 tx.estimate_predicates_ecal(params, memory, storage, ecal_handler)
664 }
665 Self::Blob(tx) => {
666 tx.estimate_predicates_ecal(params, memory, storage, ecal_handler)
667 }
668 }
669 }
670
671 async fn estimate_predicates_async_ecal<
672 Ecal: EcalHandler + Send + 'static,
673 E: ParallelExecutor,
674 >(
675 &mut self,
676 params: &CheckPredicateParams,
677 pool: &impl VmMemoryPool,
678 storage: &impl PredicateStorageProvider,
679 ecal_handler: Ecal,
680 ) -> Result<(), CheckError> {
681 match self {
682 Self::Script(tx) => {
683 tx.estimate_predicates_async_ecal::<Ecal, E>(
684 params,
685 pool,
686 storage,
687 ecal_handler,
688 )
689 .await
690 }
691 Self::Create(tx) => {
692 tx.estimate_predicates_async_ecal::<Ecal, E>(
693 params,
694 pool,
695 storage,
696 ecal_handler,
697 )
698 .await
699 }
700 Self::Mint(_) => Ok(()),
701 Self::Upgrade(tx) => {
702 tx.estimate_predicates_async_ecal::<Ecal, E>(
703 params,
704 pool,
705 storage,
706 ecal_handler,
707 )
708 .await
709 }
710 Self::Upload(tx) => {
711 tx.estimate_predicates_async_ecal::<Ecal, E>(
712 params,
713 pool,
714 storage,
715 ecal_handler,
716 )
717 .await
718 }
719 Self::Blob(tx) => {
720 tx.estimate_predicates_async_ecal::<Ecal, E>(
721 params,
722 pool,
723 storage,
724 ecal_handler,
725 )
726 .await
727 }
728 }
729 }
730}
731
732#[async_trait::async_trait]
733impl CheckPredicates for Checked<Mint> {
734 fn check_predicates(
735 mut self,
736 _params: &CheckPredicateParams,
737 _memory: impl Memory,
738 _storage: &impl PredicateStorageRequirements,
739 _ecal_handler: impl EcalHandler,
740 ) -> Result<Self, CheckError> {
741 self.checks_bitmask.insert(Checks::Predicates);
742 Ok(self)
743 }
744
745 async fn check_predicates_async<
746 Ecal: EcalHandler + Send + 'static,
747 E: ParallelExecutor,
748 >(
749 mut self,
750 _params: &CheckPredicateParams,
751 _pool: &impl VmMemoryPool,
752 _storage: &impl PredicateStorageProvider,
753 _ecal_handler: Ecal,
754 ) -> Result<Self, CheckError> {
755 self.checks_bitmask.insert(Checks::Predicates);
756 Ok(self)
757 }
758}
759
760#[async_trait::async_trait]
761impl CheckPredicates for Checked<Transaction> {
762 fn check_predicates(
763 self,
764 params: &CheckPredicateParams,
765 memory: impl Memory,
766 storage: &impl PredicateStorageRequirements,
767 ecal_handler: impl EcalHandler,
768 ) -> Result<Self, CheckError> {
769 let checked_transaction: CheckedTransaction = self.into();
770 let checked_transaction: CheckedTransaction = match checked_transaction {
771 CheckedTransaction::Script(tx) => CheckPredicates::check_predicates(
772 tx,
773 params,
774 memory,
775 storage,
776 ecal_handler,
777 )?
778 .into(),
779 CheckedTransaction::Create(tx) => CheckPredicates::check_predicates(
780 tx,
781 params,
782 memory,
783 storage,
784 ecal_handler,
785 )?
786 .into(),
787 CheckedTransaction::Mint(tx) => CheckPredicates::check_predicates(
788 tx,
789 params,
790 memory,
791 storage,
792 ecal_handler,
793 )?
794 .into(),
795 CheckedTransaction::Upgrade(tx) => CheckPredicates::check_predicates(
796 tx,
797 params,
798 memory,
799 storage,
800 ecal_handler,
801 )?
802 .into(),
803 CheckedTransaction::Upload(tx) => CheckPredicates::check_predicates(
804 tx,
805 params,
806 memory,
807 storage,
808 ecal_handler,
809 )?
810 .into(),
811 CheckedTransaction::Blob(tx) => CheckPredicates::check_predicates(
812 tx,
813 params,
814 memory,
815 storage,
816 ecal_handler,
817 )?
818 .into(),
819 };
820 Ok(checked_transaction.into())
821 }
822
823 async fn check_predicates_async<Ecal, E>(
824 mut self,
825 params: &CheckPredicateParams,
826 pool: &impl VmMemoryPool,
827 storage: &impl PredicateStorageProvider,
828 ecal_handler: Ecal,
829 ) -> Result<Self, CheckError>
830 where
831 Ecal: EcalHandler + Send + 'static,
832 E: ParallelExecutor,
833 {
834 let checked_transaction: CheckedTransaction = self.into();
835
836 let checked_transaction: CheckedTransaction = match checked_transaction {
837 CheckedTransaction::Script(tx) => CheckPredicates::check_predicates_async::<
838 Ecal,
839 E,
840 >(
841 tx, params, pool, storage, ecal_handler
842 )
843 .await?
844 .into(),
845 CheckedTransaction::Create(tx) => CheckPredicates::check_predicates_async::<
846 Ecal,
847 E,
848 >(
849 tx, params, pool, storage, ecal_handler
850 )
851 .await?
852 .into(),
853 CheckedTransaction::Mint(tx) => CheckPredicates::check_predicates_async::<
854 Ecal,
855 E,
856 >(
857 tx, params, pool, storage, ecal_handler
858 )
859 .await?
860 .into(),
861 CheckedTransaction::Upgrade(tx) => CheckPredicates::check_predicates_async::<
862 Ecal,
863 E,
864 >(
865 tx, params, pool, storage, ecal_handler
866 )
867 .await?
868 .into(),
869 CheckedTransaction::Upload(tx) => CheckPredicates::check_predicates_async::<
870 Ecal,
871 E,
872 >(
873 tx, params, pool, storage, ecal_handler
874 )
875 .await?
876 .into(),
877 CheckedTransaction::Blob(tx) => CheckPredicates::check_predicates_async::<
878 Ecal,
879 E,
880 >(
881 tx, params, pool, storage, ecal_handler
882 )
883 .await?
884 .into(),
885 };
886
887 Ok(checked_transaction.into())
888 }
889}
890
891#[derive(Debug, Clone, Eq, PartialEq, Hash)]
897#[allow(missing_docs)]
898pub enum CheckedTransaction {
899 Script(Checked<Script>),
900 Create(Checked<Create>),
901 Mint(Checked<Mint>),
902 Upgrade(Checked<Upgrade>),
903 Upload(Checked<Upload>),
904 Blob(Checked<Blob>),
905}
906
907impl From<Checked<Transaction>> for CheckedTransaction {
908 fn from(checked: Checked<Transaction>) -> Self {
909 let Checked {
910 transaction,
911 metadata,
912 checks_bitmask,
913 } = checked;
914
915 match (transaction, metadata) {
917 (Transaction::Script(transaction), CheckedMetadata::Script(metadata)) => {
918 Self::Script(Checked::new(transaction, metadata, checks_bitmask))
919 }
920 (Transaction::Create(transaction), CheckedMetadata::Create(metadata)) => {
921 Self::Create(Checked::new(transaction, metadata, checks_bitmask))
922 }
923 (Transaction::Mint(transaction), CheckedMetadata::Mint(metadata)) => {
924 Self::Mint(Checked::new(transaction, metadata, checks_bitmask))
925 }
926 (Transaction::Upgrade(transaction), CheckedMetadata::Upgrade(metadata)) => {
927 Self::Upgrade(Checked::new(transaction, metadata, checks_bitmask))
928 }
929 (Transaction::Upload(transaction), CheckedMetadata::Upload(metadata)) => {
930 Self::Upload(Checked::new(transaction, metadata, checks_bitmask))
931 }
932 (Transaction::Blob(transaction), CheckedMetadata::Blob(metadata)) => {
933 Self::Blob(Checked::new(transaction, metadata, checks_bitmask))
934 }
935 (Transaction::Script(_), _) => unreachable!(),
940 (Transaction::Create(_), _) => unreachable!(),
941 (Transaction::Mint(_), _) => unreachable!(),
942 (Transaction::Upgrade(_), _) => unreachable!(),
943 (Transaction::Upload(_), _) => unreachable!(),
944 (Transaction::Blob(_), _) => unreachable!(),
945 }
946 }
947}
948
949impl From<Checked<Script>> for CheckedTransaction {
950 fn from(checked: Checked<Script>) -> Self {
951 Self::Script(checked)
952 }
953}
954
955impl From<Checked<Create>> for CheckedTransaction {
956 fn from(checked: Checked<Create>) -> Self {
957 Self::Create(checked)
958 }
959}
960
961impl From<Checked<Mint>> for CheckedTransaction {
962 fn from(checked: Checked<Mint>) -> Self {
963 Self::Mint(checked)
964 }
965}
966
967impl From<Checked<Upgrade>> for CheckedTransaction {
968 fn from(checked: Checked<Upgrade>) -> Self {
969 Self::Upgrade(checked)
970 }
971}
972
973impl From<Checked<Upload>> for CheckedTransaction {
974 fn from(checked: Checked<Upload>) -> Self {
975 Self::Upload(checked)
976 }
977}
978
979impl From<Checked<Blob>> for CheckedTransaction {
980 fn from(checked: Checked<Blob>) -> Self {
981 Self::Blob(checked)
982 }
983}
984
985impl From<CheckedTransaction> for Checked<Transaction> {
986 fn from(checked: CheckedTransaction) -> Self {
987 match checked {
988 CheckedTransaction::Script(Checked {
989 transaction,
990 metadata,
991 checks_bitmask,
992 }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
993 CheckedTransaction::Create(Checked {
994 transaction,
995 metadata,
996 checks_bitmask,
997 }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
998 CheckedTransaction::Mint(Checked {
999 transaction,
1000 metadata,
1001 checks_bitmask,
1002 }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
1003 CheckedTransaction::Upgrade(Checked {
1004 transaction,
1005 metadata,
1006 checks_bitmask,
1007 }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
1008 CheckedTransaction::Upload(Checked {
1009 transaction,
1010 metadata,
1011 checks_bitmask,
1012 }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
1013 CheckedTransaction::Blob(Checked {
1014 transaction,
1015 metadata,
1016 checks_bitmask,
1017 }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
1018 }
1019 }
1020}
1021
1022#[derive(Debug, Clone, Eq, PartialEq, Hash)]
1024#[allow(missing_docs)]
1025pub enum CheckedMetadata {
1026 Script(<Script as IntoChecked>::Metadata),
1027 Create(<Create as IntoChecked>::Metadata),
1028 Mint(<Mint as IntoChecked>::Metadata),
1029 Upgrade(<Upgrade as IntoChecked>::Metadata),
1030 Upload(<Upload as IntoChecked>::Metadata),
1031 Blob(<Blob as IntoChecked>::Metadata),
1032}
1033
1034impl From<<Script as IntoChecked>::Metadata> for CheckedMetadata {
1035 fn from(metadata: <Script as IntoChecked>::Metadata) -> Self {
1036 Self::Script(metadata)
1037 }
1038}
1039
1040impl From<<Create as IntoChecked>::Metadata> for CheckedMetadata {
1041 fn from(metadata: <Create as IntoChecked>::Metadata) -> Self {
1042 Self::Create(metadata)
1043 }
1044}
1045
1046impl From<<Mint as IntoChecked>::Metadata> for CheckedMetadata {
1047 fn from(metadata: <Mint as IntoChecked>::Metadata) -> Self {
1048 Self::Mint(metadata)
1049 }
1050}
1051
1052impl From<<Upgrade as IntoChecked>::Metadata> for CheckedMetadata {
1053 fn from(metadata: <Upgrade as IntoChecked>::Metadata) -> Self {
1054 Self::Upgrade(metadata)
1055 }
1056}
1057
1058impl From<<Upload as IntoChecked>::Metadata> for CheckedMetadata {
1059 fn from(metadata: <Upload as IntoChecked>::Metadata) -> Self {
1060 Self::Upload(metadata)
1061 }
1062}
1063impl From<<Blob as IntoChecked>::Metadata> for CheckedMetadata {
1064 fn from(metadata: <Blob as IntoChecked>::Metadata) -> Self {
1065 Self::Blob(metadata)
1066 }
1067}
1068
1069impl IntoChecked for Transaction {
1070 type Metadata = CheckedMetadata;
1071
1072 fn into_checked_basic(
1073 self,
1074 block_height: BlockHeight,
1075 consensus_params: &ConsensusParameters,
1076 ) -> Result<Checked<Self>, CheckError> {
1077 match self {
1078 Self::Script(tx) => {
1079 let (transaction, metadata) = tx
1080 .into_checked_basic(block_height, consensus_params)?
1081 .into();
1082 Ok((transaction.into(), metadata.into()))
1083 }
1084 Self::Create(tx) => {
1085 let (transaction, metadata) = tx
1086 .into_checked_basic(block_height, consensus_params)?
1087 .into();
1088 Ok((transaction.into(), metadata.into()))
1089 }
1090 Self::Mint(tx) => {
1091 let (transaction, metadata) = tx
1092 .into_checked_basic(block_height, consensus_params)?
1093 .into();
1094 Ok((transaction.into(), metadata.into()))
1095 }
1096 Self::Upgrade(tx) => {
1097 let (transaction, metadata) = tx
1098 .into_checked_basic(block_height, consensus_params)?
1099 .into();
1100 Ok((transaction.into(), metadata.into()))
1101 }
1102 Self::Upload(tx) => {
1103 let (transaction, metadata) = tx
1104 .into_checked_basic(block_height, consensus_params)?
1105 .into();
1106 Ok((transaction.into(), metadata.into()))
1107 }
1108 Self::Blob(tx) => {
1109 let (transaction, metadata) = tx
1110 .into_checked_basic(block_height, consensus_params)?
1111 .into();
1112 Ok((transaction.into(), metadata.into()))
1113 }
1114 }
1115 .map(|(transaction, metadata)| Checked::basic(transaction, metadata))
1116 }
1117}
1118
1119impl From<ValidityError> for CheckError {
1120 fn from(value: ValidityError) -> Self {
1121 CheckError::Validity(value)
1122 }
1123}
1124
1125impl From<PredicateVerificationFailed> for CheckError {
1126 fn from(value: PredicateVerificationFailed) -> Self {
1127 CheckError::PredicateVerificationFailed(value)
1128 }
1129}
1130
1131#[cfg(feature = "random")]
1132#[allow(non_snake_case)]
1133#[allow(clippy::arithmetic_side_effects, clippy::cast_possible_truncation)]
1134#[cfg(test)]
1135mod tests {
1136
1137 use super::*;
1138 use alloc::vec;
1139 use fuel_asm::op;
1140 use fuel_crypto::SecretKey;
1141 use fuel_tx::{
1142 Script,
1143 TransactionBuilder,
1144 ValidityError,
1145 field::{
1146 ScriptGasLimit,
1147 Tip,
1148 WitnessLimit,
1149 Witnesses,
1150 },
1151 };
1152 use fuel_types::canonical::Serialize;
1153 use quickcheck::TestResult;
1154 use quickcheck_macros::quickcheck;
1155 use rand::{
1156 Rng,
1157 SeedableRng,
1158 rngs::StdRng,
1159 };
1160
1161 fn params(factor: u64) -> ConsensusParameters {
1162 ConsensusParameters::new(
1163 TxParameters::default(),
1164 PredicateParameters::default(),
1165 ScriptParameters::default(),
1166 ContractParameters::default(),
1167 FeeParameters::default().with_gas_price_factor(factor),
1168 Default::default(),
1169 Default::default(),
1170 Default::default(),
1171 Default::default(),
1172 Default::default(),
1173 Default::default(),
1174 )
1175 }
1176
1177 #[test]
1178 fn into_checked__tx_accepts_valid_tx() {
1179 let rng = &mut StdRng::seed_from_u64(2322u64);
1181 let gas_limit = 1000;
1182 let input_amount = 1000;
1183 let output_amount = 10;
1184 let max_fee_limit = 500;
1185 let tx =
1186 valid_coin_tx(rng, gas_limit, input_amount, output_amount, max_fee_limit);
1187
1188 let checked = tx
1189 .clone()
1190 .into_checked(Default::default(), &ConsensusParameters::standard())
1191 .expect("Expected valid transaction");
1192
1193 assert_eq!(checked.transaction(), &tx);
1195 assert_eq!(
1197 checked.metadata().non_retryable_balances[&AssetId::default()],
1198 input_amount - max_fee_limit - output_amount
1199 );
1200 }
1201
1202 #[test]
1203 fn into_checked__tx_accepts_valid_signed_message_coin_for_fees() {
1204 let rng = &mut StdRng::seed_from_u64(2322u64);
1206 let input_amount = 1000;
1207 let gas_limit = 1000;
1208 let zero_fee_limit = 500;
1209 let tx = signed_message_coin_tx(rng, gas_limit, input_amount, zero_fee_limit);
1210
1211 let checked = tx
1212 .into_checked(Default::default(), &ConsensusParameters::standard())
1213 .expect("Expected valid transaction");
1214
1215 assert_eq!(
1217 checked.metadata().non_retryable_balances[&AssetId::default()],
1218 input_amount - checked.transaction.max_fee_limit()
1219 );
1220 }
1221
1222 #[test]
1223 fn into_checked__tx_excludes_message_output_amount_from_fee() {
1224 let rng = &mut StdRng::seed_from_u64(2322u64);
1226 let input_amount = 100;
1227 let gas_limit = 1000;
1228 let zero_fee_limit = 50;
1229 let tx = signed_message_coin_tx(rng, gas_limit, input_amount, zero_fee_limit);
1230
1231 let checked = tx
1232 .into_checked(Default::default(), &ConsensusParameters::standard())
1233 .expect("Expected valid transaction");
1234
1235 assert_eq!(
1237 checked.metadata().non_retryable_balances[&AssetId::default()],
1238 input_amount - checked.transaction.max_fee_limit()
1239 );
1240 }
1241
1242 #[test]
1243 fn into_checked__message_data_signed_message_is_not_used_to_cover_fees() {
1244 let rng = &mut StdRng::seed_from_u64(2322u64);
1245
1246 let input_amount = 100;
1248
1249 let max_fee = input_amount;
1251 let tx = TransactionBuilder::script(vec![], vec![])
1252 .max_fee_limit(max_fee)
1253 .add_unsigned_message_input(SecretKey::random(rng), rng.r#gen(), rng.r#gen(), input_amount, vec![0xff; 10])
1255 .add_unsigned_coin_input(SecretKey::random(rng), rng.r#gen(), 0, AssetId::BASE, rng.r#gen())
1257 .finalize();
1258
1259 let err = tx
1260 .into_checked(Default::default(), &ConsensusParameters::standard())
1261 .expect_err("Expected valid transaction");
1262
1263 assert!(matches!(
1265 err,
1266 CheckError::Validity(ValidityError::InsufficientFeeAmount {
1267 expected: _,
1268 provided: 0
1269 })
1270 ));
1271 }
1272
1273 #[test]
1274 fn message_data_predicate_message_is_not_used_to_cover_fees() {
1275 let rng = &mut StdRng::seed_from_u64(2322u64);
1276 let gas_limit = 1000;
1277
1278 let input_amount = 100;
1280
1281 let max_fee = input_amount;
1283
1284 let tx = TransactionBuilder::script(vec![], vec![])
1285 .max_fee_limit(max_fee)
1286 .script_gas_limit(gas_limit)
1287 .add_input(Input::message_data_predicate(
1288 rng.r#gen(),
1289 rng.r#gen(),
1290 input_amount,
1291 rng.r#gen(),
1292 Default::default(),
1293 vec![0xff; 10],
1294 vec![0xaa; 10],
1295 vec![0xbb; 10],
1296 ))
1297 .add_unsigned_coin_input(SecretKey::random(rng), rng.r#gen(), 0, AssetId::BASE, rng.r#gen())
1299 .finalize();
1300
1301 let err = tx
1302 .into_checked(Default::default(), &ConsensusParameters::standard())
1303 .expect_err("Expected valid transaction");
1304
1305 assert!(matches!(
1307 err,
1308 CheckError::Validity(ValidityError::InsufficientFeeAmount {
1309 expected: _,
1310 provided: 0
1311 })
1312 ));
1313 }
1314
1315 #[quickcheck]
1318 fn max_fee_coin_input(
1319 gas_price: u64,
1320 gas_limit: u64,
1321 witness_limit: u64,
1322 input_amount: u64,
1323 gas_price_factor: u64,
1324 seed: u64,
1325 ) -> TestResult {
1326 if gas_price_factor == 0 {
1330 return TestResult::discard();
1331 }
1332
1333 let rng = &mut StdRng::seed_from_u64(seed);
1334 let gas_costs = GasCosts::default();
1335 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(gas_price_factor);
1336 let predicate_gas_used = rng.r#gen();
1337 let tx = predicate_tx(
1338 rng,
1339 gas_limit,
1340 witness_limit,
1341 input_amount,
1342 predicate_gas_used,
1343 );
1344
1345 if let Ok(valid) = is_valid_max_fee(&tx, gas_price, &gas_costs, &fee_params) {
1346 TestResult::from_bool(valid)
1347 } else {
1348 TestResult::discard()
1349 }
1350 }
1351
1352 #[quickcheck]
1355 fn min_fee_coin_input(
1356 gas_price: u64,
1357 gas_limit: u64,
1358 witness_limit: u64,
1359 input_amount: u64,
1360 gas_price_factor: u64,
1361 seed: u64,
1362 ) -> TestResult {
1363 if gas_price_factor == 0 {
1367 return TestResult::discard();
1368 }
1369 let rng = &mut StdRng::seed_from_u64(seed);
1370 let gas_costs = GasCosts::default();
1371 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(gas_price_factor);
1372 let predicate_gas_used = rng.r#gen();
1373 let tx = predicate_tx(
1374 rng,
1375 gas_limit,
1376 witness_limit,
1377 input_amount,
1378 predicate_gas_used,
1379 );
1380
1381 if let Ok(valid) = is_valid_max_fee(&tx, gas_price, &gas_costs, &fee_params) {
1382 TestResult::from_bool(valid)
1383 } else {
1384 TestResult::discard()
1385 }
1386 }
1387
1388 #[quickcheck]
1391 fn max_fee_message_input(
1392 gas_price: u64,
1393 gas_limit: u64,
1394 input_amount: u64,
1395 gas_price_factor: u64,
1396 tip: u64,
1397 seed: u64,
1398 ) -> TestResult {
1399 if gas_price_factor == 0 {
1401 return TestResult::discard();
1402 }
1403
1404 let rng = &mut StdRng::seed_from_u64(seed);
1405 let gas_costs = GasCosts::default();
1406 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(gas_price_factor);
1407 let tx = predicate_message_coin_tx(rng, gas_limit, input_amount, tip);
1408
1409 if let Ok(valid) = is_valid_max_fee(&tx, gas_price, &gas_costs, &fee_params) {
1410 TestResult::from_bool(valid)
1411 } else {
1412 TestResult::discard()
1413 }
1414 }
1415
1416 #[quickcheck]
1418 fn refund_when_used_gas_is_zero(
1419 gas_price: u64,
1420 gas_limit: u64,
1421 input_amount: u64,
1422 gas_price_factor: u64,
1423 seed: u64,
1424 tip: u64,
1425 ) -> TestResult {
1426 if gas_price_factor == 0 {
1428 return TestResult::discard();
1429 }
1430
1431 let rng = &mut StdRng::seed_from_u64(seed);
1432 let gas_costs = GasCosts::default();
1433 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(gas_price_factor);
1434 let tx = predicate_message_coin_tx(rng, gas_limit, input_amount, tip);
1435
1436 let used_gas = 0;
1438
1439 let refund = tx.refund_fee(&gas_costs, &fee_params, used_gas, gas_price);
1441
1442 let min_fee = tx.min_fee(&gas_costs, &fee_params, gas_price);
1443 let max_fee = tx.max_fee(&gas_costs, &fee_params, gas_price);
1444
1445 if let Some(refund) = refund {
1447 TestResult::from_bool(max_fee - min_fee == refund as u128)
1448 } else {
1449 TestResult::discard()
1450 }
1451 }
1452
1453 #[quickcheck]
1456 fn min_fee_message_input(
1457 gas_limit: u64,
1458 input_amount: u64,
1459 gas_price: u64,
1460 gas_price_factor: u64,
1461 tip: u64,
1462 seed: u64,
1463 ) -> TestResult {
1464 if gas_price_factor == 0 {
1468 return TestResult::discard();
1469 }
1470 let rng = &mut StdRng::seed_from_u64(seed);
1471 let gas_costs = GasCosts::default();
1472 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(gas_price_factor);
1473 let tx = predicate_message_coin_tx(rng, gas_limit, input_amount, tip);
1474
1475 if let Ok(valid) = is_valid_min_fee(&tx, &gas_costs, &fee_params, gas_price) {
1476 TestResult::from_bool(valid)
1477 } else {
1478 TestResult::discard()
1479 }
1480 }
1481
1482 #[test]
1483 fn fee_multiple_signed_inputs() {
1484 let rng = &mut StdRng::seed_from_u64(2322u64);
1485 let gas_price = 100;
1486 let gas_limit = 1000;
1487 let gas_costs = GasCosts::default();
1488 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(1);
1489 let tx = TransactionBuilder::script(vec![], vec![])
1490 .script_gas_limit(gas_limit)
1491 .add_unsigned_message_input(
1493 SecretKey::random(rng),
1494 rng.r#gen(),
1495 rng.r#gen(),
1496 rng.r#gen::<u32>() as u64,
1497 vec![],
1498 )
1499 .add_unsigned_message_input(
1500 SecretKey::random(rng),
1501 rng.r#gen(),
1502 rng.r#gen(),
1503 rng.r#gen::<u32>() as u64,
1504 vec![],
1505 )
1506 .add_unsigned_message_input(
1507 SecretKey::random(rng),
1508 rng.r#gen(),
1509 rng.r#gen(),
1510 rng.r#gen::<u32>() as u64,
1511 vec![],
1512 )
1513 .finalize();
1514 let fee =
1515 TransactionFee::checked_from_tx(&gas_costs, &fee_params, &tx, gas_price)
1516 .unwrap();
1517
1518 let min_fee = fee.min_fee();
1519 let expected_min_fee = (tx.metered_bytes_size() as u64
1520 * fee_params.gas_per_byte()
1521 + gas_costs.vm_initialization().resolve(tx.size() as u64)
1522 + 3 * gas_costs.eck1()
1523 + gas_costs.s256().resolve(tx.size() as u64))
1524 * gas_price;
1525 assert_eq!(min_fee, expected_min_fee);
1526
1527 let max_fee = fee.max_fee();
1528 let expected_max_fee = expected_min_fee + gas_limit * gas_price;
1529 assert_eq!(max_fee, expected_max_fee);
1530 }
1531
1532 #[test]
1533 fn fee_multiple_signed_inputs_single_owner() {
1534 let rng = &mut StdRng::seed_from_u64(2322u64);
1535 let gas_price = 100;
1536 let gas_limit = 1000;
1537 let gas_costs = GasCosts::default();
1538 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(1);
1539 let secret = SecretKey::random(rng);
1540 let tx = TransactionBuilder::script(vec![], vec![])
1541 .script_gas_limit(gas_limit)
1542 .add_unsigned_message_input(
1544 secret,
1545 rng.r#gen(),
1546 rng.r#gen(),
1547 rng.r#gen::<u32>() as u64,
1548 vec![],
1549 )
1550 .add_unsigned_message_input(
1551 secret,
1552 rng.r#gen(),
1553 rng.r#gen(),
1554 rng.r#gen::<u32>() as u64,
1555 vec![],
1556 )
1557 .add_unsigned_message_input(
1558 secret,
1559 rng.r#gen(),
1560 rng.r#gen(),
1561 rng.r#gen::<u32>() as u64,
1562 vec![],
1563 )
1564 .finalize();
1565 let fee =
1566 TransactionFee::checked_from_tx(&gas_costs, &fee_params, &tx, gas_price)
1567 .unwrap();
1568
1569 let min_fee = fee.min_fee();
1570 let expected_min_fee = (tx.metered_bytes_size() as u64
1574 * fee_params.gas_per_byte()
1575 + gas_costs.vm_initialization().resolve(tx.size() as u64)
1576 + gas_costs.eck1()
1577 + gas_costs.s256().resolve(tx.size() as u64))
1578 * gas_price;
1579 assert_eq!(min_fee, expected_min_fee);
1580
1581 let max_fee = fee.max_fee();
1582 let expected_max_fee = min_fee + gas_limit * gas_price;
1583 assert_eq!(max_fee, expected_max_fee);
1584 }
1585
1586 fn random_bytes<const N: usize, R: Rng + ?Sized>(rng: &mut R) -> Box<[u8; N]> {
1587 let mut bytes = Box::new([0u8; N]);
1588 for chunk in bytes.chunks_mut(32) {
1589 rng.fill(chunk);
1590 }
1591 bytes
1592 }
1593
1594 #[test]
1595 fn min_fee_multiple_predicate_inputs() {
1596 let rng = &mut StdRng::seed_from_u64(2322u64);
1597 let gas_price = 100;
1598 let gas_limit = 1000;
1599 let gas_costs = GasCosts::default();
1600 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(1);
1601 let predicate_1 = random_bytes::<1024, _>(rng);
1602 let predicate_2 = random_bytes::<2048, _>(rng);
1603 let predicate_3 = random_bytes::<4096, _>(rng);
1604 let tx = TransactionBuilder::script(vec![], vec![])
1605 .script_gas_limit(gas_limit)
1606 .add_input(Input::message_coin_predicate(
1608 rng.r#gen(),
1609 rng.r#gen(),
1610 rng.r#gen(),
1611 rng.r#gen(),
1612 50,
1613 predicate_1.to_vec(),
1614 vec![],
1615 ))
1616 .add_input(Input::message_coin_predicate(
1617 rng.r#gen(),
1618 rng.r#gen(),
1619 rng.r#gen(),
1620 rng.r#gen(),
1621 100,
1622 predicate_2.to_vec(),
1623 vec![],
1624 ))
1625 .add_input(Input::message_coin_predicate(
1626 rng.r#gen(),
1627 rng.r#gen(),
1628 rng.r#gen(),
1629 rng.r#gen(),
1630 200,
1631 predicate_3.to_vec(),
1632 vec![],
1633 ))
1634 .finalize();
1635 let fee =
1636 TransactionFee::checked_from_tx(&gas_costs, &fee_params, &tx, gas_price)
1637 .unwrap();
1638
1639 let min_fee = fee.min_fee();
1640 let expected_min_fee = (tx.size() as u64 * fee_params.gas_per_byte()
1641 + gas_costs.vm_initialization().resolve(tx.size() as u64)
1642 + gas_costs.contract_root().resolve(predicate_1.len() as u64)
1643 + gas_costs.contract_root().resolve(predicate_2.len() as u64)
1644 + gas_costs.contract_root().resolve(predicate_3.len() as u64)
1645 + 3 * gas_costs.vm_initialization().resolve(tx.size() as u64)
1646 + 50
1647 + 100
1648 + 200
1649 + gas_costs.s256().resolve(tx.size() as u64))
1650 * gas_price;
1651 assert_eq!(min_fee, expected_min_fee);
1652
1653 let max_fee = fee.max_fee();
1654 let expected_max_fee = min_fee + gas_limit * gas_price;
1655 assert_eq!(max_fee, expected_max_fee);
1656 }
1657
1658 #[test]
1659 fn min_fee_multiple_signed_and_predicate_inputs() {
1660 let rng = &mut StdRng::seed_from_u64(2322u64);
1661 let gas_price = 100;
1662 let gas_limit = 1000;
1663 let gas_costs = GasCosts::default();
1664 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(1);
1665 let predicate_1 = random_bytes::<1024, _>(rng);
1666 let predicate_2 = random_bytes::<2048, _>(rng);
1667 let predicate_3 = random_bytes::<4096, _>(rng);
1668 let tx = TransactionBuilder::script(vec![], vec![])
1669 .script_gas_limit(gas_limit)
1670 .add_unsigned_message_input(
1672 SecretKey::random(rng),
1673 rng.r#gen(),
1674 rng.r#gen(),
1675 rng.r#gen::<u32>() as u64,
1676 vec![],
1677 )
1678 .add_unsigned_message_input(
1679 SecretKey::random(rng),
1680 rng.r#gen(),
1681 rng.r#gen(),
1682 rng.r#gen::<u32>() as u64,
1683 vec![],
1684 )
1685 .add_unsigned_message_input(
1686 SecretKey::random(rng),
1687 rng.r#gen(),
1688 rng.r#gen(),
1689 rng.r#gen::<u32>() as u64,
1690 vec![],
1691 )
1692 .add_input(Input::message_coin_predicate(
1694 rng.r#gen(),
1695 rng.r#gen(),
1696 rng.r#gen(),
1697 rng.r#gen(),
1698 50,
1699 predicate_1.to_vec(),
1700 vec![],
1701 ))
1702 .add_input(Input::message_coin_predicate(
1703 rng.r#gen(),
1704 rng.r#gen(),
1705 rng.r#gen(),
1706 rng.r#gen(),
1707 100,
1708 predicate_2.to_vec(),
1709 vec![],
1710 ))
1711 .add_input(Input::message_coin_predicate(
1712 rng.r#gen(),
1713 rng.r#gen(),
1714 rng.r#gen(),
1715 rng.r#gen(),
1716 200,
1717 predicate_3.to_vec(),
1718 vec![],
1719 ))
1720 .finalize();
1721 let fee =
1722 TransactionFee::checked_from_tx(&gas_costs, &fee_params, &tx, gas_price)
1723 .unwrap();
1724
1725 let min_fee = fee.min_fee();
1726 let expected_min_fee = (tx.metered_bytes_size() as u64
1727 * fee_params.gas_per_byte()
1728 + 3 * gas_costs.eck1()
1729 + gas_costs.vm_initialization().resolve(tx.size() as u64)
1730 + gas_costs.contract_root().resolve(predicate_1.len() as u64)
1731 + gas_costs.contract_root().resolve(predicate_2.len() as u64)
1732 + gas_costs.contract_root().resolve(predicate_3.len() as u64)
1733 + 3 * gas_costs.vm_initialization().resolve(tx.size() as u64)
1734 + 50
1735 + 100
1736 + 200
1737 + gas_costs.s256().resolve(tx.size() as u64))
1738 * gas_price;
1739 assert_eq!(min_fee, expected_min_fee);
1740
1741 let max_fee = fee.max_fee();
1742 let expected_max_fee = min_fee + gas_limit * gas_price;
1743 assert_eq!(max_fee, expected_max_fee);
1744 }
1745
1746 #[test]
1747 fn fee_create_tx() {
1748 let rng = &mut StdRng::seed_from_u64(2322u64);
1749 let gas_price = 100;
1750 let witness_limit = 1000;
1751 let gas_costs = GasCosts::default();
1752 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(1);
1753 let gen_storage_slot = || rng.r#gen::<StorageSlot>();
1754 let storage_slots = core::iter::repeat_with(gen_storage_slot)
1755 .take(100)
1756 .collect::<Vec<_>>();
1757 let storage_slots_len = storage_slots.len();
1758 let bytecode = rng.r#gen::<Witness>();
1759 let bytecode_len = bytecode.as_ref().len();
1760 let salt = rng.r#gen::<Salt>();
1761 let tx = TransactionBuilder::create(bytecode.clone(), salt, storage_slots)
1762 .witness_limit(witness_limit)
1763 .finalize();
1764 let fee =
1765 TransactionFee::checked_from_tx(&gas_costs, &fee_params, &tx, gas_price)
1766 .unwrap();
1767
1768 let min_fee = fee.min_fee();
1769 let expected_min_fee = (tx.metered_bytes_size() as u64
1770 * fee_params.gas_per_byte()
1771 + gas_costs.state_root().resolve(storage_slots_len as Word)
1772 + gas_costs.contract_root().resolve(bytecode_len as Word)
1773 + gas_costs.vm_initialization().resolve(tx.size() as u64)
1774 + gas_costs.s256().resolve(100)
1775 + gas_costs.s256().resolve(tx.size() as u64))
1776 * gas_price;
1777 assert_eq!(min_fee, expected_min_fee);
1778
1779 let max_fee = fee.max_fee();
1780 let expected_max_fee = min_fee
1781 + (witness_limit - bytecode.size() as u64)
1782 * fee_params.gas_per_byte()
1783 * gas_price;
1784 assert_eq!(max_fee, expected_max_fee);
1785 }
1786
1787 #[test]
1788 fn fee_create_tx_no_bytecode() {
1789 let rng = &mut StdRng::seed_from_u64(2322u64);
1790 let gas_price = 100;
1791 let witness_limit = 1000;
1792 let gas_costs = GasCosts::default();
1793 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(1);
1794 let bytecode: Witness = Vec::<u8>::new().into();
1795 let salt = rng.r#gen::<Salt>();
1796 let tx = TransactionBuilder::create(bytecode.clone(), salt, vec![])
1797 .witness_limit(witness_limit)
1798 .finalize();
1799 let fee =
1800 TransactionFee::checked_from_tx(&gas_costs, &fee_params, &tx, gas_price)
1801 .unwrap();
1802
1803 let min_fee = fee.min_fee();
1804 let expected_min_fee = (tx.metered_bytes_size() as u64
1805 * fee_params.gas_per_byte()
1806 + gas_costs.state_root().resolve(0)
1807 + gas_costs.contract_root().resolve(0)
1808 + gas_costs.vm_initialization().resolve(tx.size() as u64)
1809 + gas_costs.s256().resolve(100)
1810 + gas_costs.s256().resolve(tx.size() as u64))
1811 * gas_price;
1812 assert_eq!(min_fee, expected_min_fee);
1813
1814 let max_fee = fee.max_fee();
1815 let expected_max_fee = min_fee
1816 + (witness_limit - bytecode.size_static() as u64)
1817 * fee_params.gas_per_byte()
1818 * gas_price;
1819 assert_eq!(max_fee, expected_max_fee);
1820 }
1821
1822 #[test]
1823 fn checked_tx_rejects_invalid_tx() {
1824 let rng = &mut StdRng::seed_from_u64(2322u64);
1826 let asset = rng.r#gen();
1827 let gas_limit = 100;
1828 let input_amount = 1_000;
1829
1830 let tx = TransactionBuilder::script(vec![], vec![])
1832 .script_gas_limit(gas_limit)
1833 .add_input(Input::coin_signed(
1834 rng.r#gen(),
1835 rng.r#gen(),
1836 input_amount,
1837 asset,
1838 rng.r#gen(),
1839 Default::default(),
1840 ))
1841 .add_input(Input::contract(
1842 rng.r#gen(),
1843 rng.r#gen(),
1844 rng.r#gen(),
1845 rng.r#gen(),
1846 rng.r#gen(),
1847 ))
1848 .add_output(Output::contract(1, rng.r#gen(), rng.r#gen()))
1849 .add_output(Output::coin(rng.r#gen(), 10, asset))
1850 .add_output(Output::change(rng.r#gen(), 0, asset))
1851 .add_witness(Default::default())
1852 .finalize();
1853
1854 let err = tx
1855 .into_checked(Default::default(), &ConsensusParameters::standard())
1856 .expect_err("Expected invalid transaction");
1857
1858 assert!(matches!(
1860 err,
1861 CheckError::Validity(ValidityError::InputInvalidSignature { .. })
1862 ));
1863 }
1864
1865 #[test]
1866 fn into_checked__tx_fails_when_provided_fees_dont_cover_byte_costs() {
1867 let rng = &mut StdRng::seed_from_u64(2322u64);
1868
1869 let arb_input_amount = 1;
1870 let gas_price = 2; let gas_limit = 0; let factor = 1;
1873 let zero_max_fee = 0;
1874 let params = params(factor);
1875
1876 let transaction = base_asset_tx(rng, arb_input_amount, gas_limit, zero_max_fee);
1878 transaction
1879 .clone()
1880 .into_checked(Default::default(), ¶ms)
1881 .unwrap();
1882 let fees = TransactionFee::checked_from_tx(
1883 &GasCosts::default(),
1884 params.fee_params(),
1885 &transaction,
1886 gas_price,
1887 )
1888 .unwrap();
1889 let real_max_fee = fees.max_fee();
1890
1891 let new_input_amount = real_max_fee;
1892 let mut new_transaction =
1893 base_asset_tx(rng, new_input_amount, gas_limit, real_max_fee);
1894 new_transaction
1895 .clone()
1896 .into_checked(Default::default(), ¶ms)
1897 .unwrap()
1898 .into_ready(gas_price, &GasCosts::default(), params.fee_params(), None)
1899 .expect("`new_transaction` should be fully valid");
1900
1901 new_transaction.witnesses_mut().push(rng.r#gen());
1904 let bigger_checked = new_transaction
1905 .into_checked(Default::default(), ¶ms)
1906 .unwrap();
1907
1908 let err = bigger_checked
1910 .into_ready(gas_price, &GasCosts::default(), params.fee_params(), None)
1911 .expect_err("Expected invalid transaction");
1912
1913 let max_fee_from_policies = match err {
1914 CheckError::InsufficientMaxFee {
1915 max_fee_from_policies,
1916 ..
1917 } => max_fee_from_policies,
1918 _ => panic!("expected insufficient max fee; found {err:?}"),
1919 };
1920
1921 assert_eq!(max_fee_from_policies, real_max_fee);
1923 }
1924
1925 #[test]
1926 fn into_checked__tx_fails_when_provided_fees_dont_cover_fee_limit() {
1927 let rng = &mut StdRng::seed_from_u64(2322u64);
1928
1929 let input_amount = 10;
1930 let factor = 1;
1931 let gas_limit = input_amount + 1; let input_amount = 10;
1936 let big_fee_limit = input_amount + 1;
1937
1938 let transaction = base_asset_tx(rng, input_amount, gas_limit, big_fee_limit);
1939
1940 let consensus_params = params(factor);
1941
1942 let err = transaction
1944 .into_checked(Default::default(), &consensus_params)
1945 .expect_err("overflow expected");
1946
1947 let provided = match err {
1949 CheckError::Validity(ValidityError::InsufficientFeeAmount {
1950 provided,
1951 ..
1952 }) => provided,
1953 _ => panic!("expected insufficient fee amount; found {err:?}"),
1954 };
1955 assert_eq!(provided, input_amount);
1956 }
1957
1958 #[test]
1959 fn into_ready__bytes_fee_cant_overflow() {
1960 let rng = &mut StdRng::seed_from_u64(2322u64);
1961
1962 let input_amount = 1000;
1963 let max_gas_price = Word::MAX;
1964 let gas_limit = 0; let zero_fee_limit = 0;
1966 let transaction = base_asset_tx(rng, input_amount, gas_limit, zero_fee_limit);
1967 let gas_costs = GasCosts::default();
1968
1969 let consensus_params = params(1);
1970
1971 let fee_params = consensus_params.fee_params();
1972 let err = transaction
1973 .into_checked(Default::default(), &consensus_params)
1974 .unwrap()
1975 .into_ready(max_gas_price, &gas_costs, fee_params, None)
1976 .expect_err("overflow expected");
1977
1978 assert_eq!(err, CheckError::Validity(ValidityError::BalanceOverflow));
1979 }
1980
1981 #[test]
1982 fn into_ready__fails_if_fee_limit_too_low() {
1983 let rng = &mut StdRng::seed_from_u64(2322u64);
1984
1985 let input_amount = 1000;
1986 let gas_price = 100;
1987 let gas_limit = 0; let gas_costs = GasCosts::default();
1989
1990 let consensus_params = params(1);
1991
1992 let fee_params = consensus_params.fee_params();
1993
1994 let zero_fee_limit = 0;
1996 let transaction = base_asset_tx(rng, input_amount, gas_limit, zero_fee_limit);
1997
1998 let err = transaction
2000 .into_checked(Default::default(), &consensus_params)
2001 .unwrap()
2002 .into_ready(gas_price, &gas_costs, fee_params, None)
2003 .expect_err("overflow expected");
2004
2005 assert!(matches!(err, CheckError::InsufficientMaxFee { .. }));
2007 }
2008
2009 #[test]
2010 fn into_ready__tx_fails_if_tip_not_covered() {
2011 let rng = &mut StdRng::seed_from_u64(2322u64);
2012
2013 let input_amount = 1;
2015 let gas_limit = 1000;
2016 let params = ConsensusParameters::standard();
2017 let block_height = 1.into();
2018 let gas_costs = GasCosts::default();
2019 let max_fee_limit = input_amount;
2020 let gas_price = 1;
2021
2022 let tx_without_tip =
2023 base_asset_tx_with_tip(rng, input_amount, gas_limit, max_fee_limit, None);
2024 tx_without_tip
2025 .clone()
2026 .into_checked(block_height, ¶ms)
2027 .unwrap()
2028 .into_ready(gas_price, &gas_costs, params.fee_params(), None)
2029 .expect("Should be valid");
2030
2031 let tip = 100;
2033 let tx_without_enough_to_pay_for_tip = base_asset_tx_with_tip(
2034 rng,
2035 input_amount,
2036 gas_limit,
2037 max_fee_limit,
2038 Some(tip),
2039 );
2040 tx_without_enough_to_pay_for_tip
2041 .into_checked(block_height, ¶ms)
2042 .unwrap()
2043 .into_ready(gas_price, &gas_costs, params.fee_params(), None)
2044 .expect_err("Expected invalid transaction");
2045
2046 let new_input_amount = input_amount + tip;
2048 let new_gas_limit = new_input_amount;
2049 let tx = base_asset_tx_with_tip(
2050 rng,
2051 new_input_amount,
2052 gas_limit,
2053 new_gas_limit,
2054 Some(tip),
2055 );
2056
2057 tx.clone()
2059 .into_checked(block_height, ¶ms)
2060 .unwrap()
2061 .into_ready(gas_price, &GasCosts::default(), params.fee_params(), None)
2062 .expect("Should be valid");
2063 }
2064
2065 #[test]
2066 fn into_ready__return_overflow_error_if_gas_price_too_high() {
2067 let rng = &mut StdRng::seed_from_u64(2322u64);
2068 let input_amount = 1000;
2069 let gas_price = Word::MAX;
2070 let gas_limit = 2; let max_fee_limit = 0;
2072
2073 let transaction = base_asset_tx(rng, input_amount, gas_limit, max_fee_limit);
2074
2075 let consensus_params = params(1);
2076
2077 let err = transaction
2078 .into_checked(Default::default(), &consensus_params)
2079 .unwrap()
2080 .into_ready(
2081 gas_price,
2082 &GasCosts::default(),
2083 consensus_params.fee_params(),
2084 None,
2085 )
2086 .expect_err("overflow expected");
2087
2088 assert_eq!(err, CheckError::Validity(ValidityError::BalanceOverflow));
2089 }
2090
2091 #[test]
2092 fn checked_tx_fails_if_asset_is_overspent_by_coin_output() {
2093 let input_amount = 1_000;
2094 let rng = &mut StdRng::seed_from_u64(2322u64);
2095 let secret = SecretKey::random(rng);
2096 let any_asset = rng.r#gen();
2097 let tx = TransactionBuilder::script(vec![], vec![])
2098 .script_gas_limit(100)
2099 .add_unsigned_coin_input(
2101 secret,
2102 rng.r#gen(),
2103 input_amount,
2104 AssetId::default(),
2105 rng.r#gen(),
2106 )
2107 .add_output(Output::change(rng.r#gen(), 0, AssetId::default()))
2108 .add_unsigned_coin_input(
2110 secret,
2111 rng.r#gen(),
2112 input_amount,
2113 any_asset,
2114 rng.r#gen(),
2115 )
2116 .add_output(Output::coin(rng.r#gen(), input_amount + 1, any_asset))
2117 .add_output(Output::change(rng.r#gen(), 0, any_asset))
2118 .finalize();
2119
2120 let checked = tx
2121 .into_checked(Default::default(), &ConsensusParameters::standard())
2122 .expect_err("Expected valid transaction");
2123
2124 assert_eq!(
2125 CheckError::Validity(ValidityError::InsufficientInputAmount {
2126 asset: any_asset,
2127 expected: input_amount + 1,
2128 provided: input_amount,
2129 }),
2130 checked
2131 );
2132 }
2133
2134 #[cfg(feature = "std")]
2135 #[test]
2136 fn basic_check_marks_basic_flag() {
2137 let block_height = 1.into();
2138
2139 let tx = Transaction::default_test_tx();
2140 let checked = tx
2142 .into_checked_basic(block_height, &ConsensusParameters::standard())
2143 .unwrap();
2144 assert!(checked.checks().contains(Checks::Basic));
2145 }
2146
2147 #[test]
2148 fn signatures_check_marks_signatures_flag() {
2149 let mut rng = StdRng::seed_from_u64(1);
2150 let block_height = 1.into();
2151 let max_fee_limit = 0;
2152
2153 let tx = valid_coin_tx(&mut rng, 100000, 1000000, 10, max_fee_limit);
2154 let chain_id = ChainId::default();
2155 let checked = tx
2156 .into_checked(
2158 block_height,
2159 &ConsensusParameters::standard_with_id(chain_id),
2160 )
2161 .unwrap()
2162 .check_signatures(&chain_id)
2164 .unwrap();
2165
2166 assert!(
2167 checked
2168 .checks()
2169 .contains(Checks::Basic | Checks::Signatures)
2170 );
2171 }
2172
2173 #[test]
2174 fn predicates_check_marks_predicate_flag() {
2175 let mut rng = StdRng::seed_from_u64(1);
2176 let block_height = 1.into();
2177 let gas_costs = GasCosts::default();
2178
2179 let tx = predicate_tx(&mut rng, 1000000, 1000000, 1000000, gas_costs.ret());
2180
2181 let mut consensus_params = ConsensusParameters::standard();
2182 consensus_params.set_gas_costs(gas_costs);
2183
2184 let check_predicate_params = CheckPredicateParams::from(&consensus_params);
2185
2186 let checked = tx
2187 .into_checked(
2189 block_height,
2190 &consensus_params,
2191 )
2192 .unwrap()
2193 .check_predicates(&check_predicate_params, MemoryInstance::new(), &EmptyStorage, NotSupportedEcal)
2195 .unwrap();
2196 assert!(
2197 checked
2198 .checks()
2199 .contains(Checks::Basic | Checks::Predicates)
2200 );
2201 }
2202
2203 fn is_valid_max_fee(
2204 tx: &Script,
2205 gas_price: u64,
2206 gas_costs: &GasCosts,
2207 fee_params: &FeeParameters,
2208 ) -> Result<bool, ValidityError> {
2209 fn gas_to_fee(gas: u64, price: u64, factor: u64) -> u128 {
2210 let prices_gas = gas as u128 * price as u128;
2211 let fee = prices_gas / factor as u128;
2212 let fee_remainder = (prices_gas.rem_euclid(factor as u128) > 0) as u128;
2213 fee + fee_remainder
2214 }
2215
2216 let gas_used_by_bytes = fee_params
2218 .gas_per_byte()
2219 .saturating_mul(tx.metered_bytes_size() as u64);
2220 let gas_used_by_inputs = tx.gas_used_by_inputs(gas_costs);
2221 let gas_used_by_metadata = tx.gas_used_by_metadata(gas_costs);
2222 let min_gas = gas_used_by_bytes
2223 .saturating_add(gas_used_by_inputs)
2224 .saturating_add(gas_used_by_metadata)
2225 .saturating_add(
2226 gas_costs
2227 .vm_initialization()
2228 .resolve(tx.metered_bytes_size() as u64),
2229 );
2230
2231 let witness_limit_allowance = tx
2233 .witness_limit()
2234 .saturating_sub(tx.witnesses().size_dynamic() as u64)
2235 .saturating_mul(fee_params.gas_per_byte());
2236 let max_gas = min_gas
2237 .saturating_add(*tx.script_gas_limit())
2238 .saturating_add(witness_limit_allowance);
2239 let max_fee = gas_to_fee(max_gas, gas_price, fee_params.gas_price_factor());
2240
2241 let max_fee_with_tip = max_fee.saturating_add(tx.tip() as u128);
2242
2243 let result = max_fee_with_tip == tx.max_fee(gas_costs, fee_params, gas_price);
2244 Ok(result)
2245 }
2246
2247 fn is_valid_min_fee<Tx>(
2248 tx: &Tx,
2249 gas_costs: &GasCosts,
2250 fee_params: &FeeParameters,
2251 gas_price: u64,
2252 ) -> Result<bool, ValidityError>
2253 where
2254 Tx: Chargeable + field::Inputs + field::Outputs,
2255 {
2256 let gas_used_by_bytes = fee_params
2259 .gas_per_byte()
2260 .saturating_mul(tx.metered_bytes_size() as u64);
2261 let gas_used_by_inputs = tx.gas_used_by_inputs(gas_costs);
2262 let gas_used_by_metadata = tx.gas_used_by_metadata(gas_costs);
2263 let gas = gas_used_by_bytes
2264 .saturating_add(gas_used_by_inputs)
2265 .saturating_add(gas_used_by_metadata)
2266 .saturating_add(
2267 gas_costs
2268 .vm_initialization()
2269 .resolve(tx.metered_bytes_size() as u64),
2270 );
2271 let total = gas as u128 * gas_price as u128;
2272 let fee = total / fee_params.gas_price_factor() as u128;
2274 let fee_remainder =
2275 (total.rem_euclid(fee_params.gas_price_factor() as u128) > 0) as u128;
2276 let rounded_fee = fee
2277 .saturating_add(fee_remainder)
2278 .saturating_add(tx.tip() as u128);
2279 let min_fee = rounded_fee;
2280 let calculated_min_fee = tx.min_fee(gas_costs, fee_params, gas_price);
2281
2282 Ok(min_fee == calculated_min_fee)
2283 }
2284
2285 fn valid_coin_tx(
2286 rng: &mut StdRng,
2287 gas_limit: u64,
2288 input_amount: u64,
2289 output_amount: u64,
2290 max_fee_limit: u64,
2291 ) -> Script {
2292 let asset = AssetId::default();
2293 TransactionBuilder::script(vec![], vec![])
2294 .script_gas_limit(gas_limit)
2295 .max_fee_limit(max_fee_limit)
2296 .add_unsigned_coin_input(
2297 SecretKey::random(rng),
2298 rng.r#gen(),
2299 input_amount,
2300 asset,
2301 rng.r#gen(),
2302 )
2303 .add_input(Input::contract(
2304 rng.r#gen(),
2305 rng.r#gen(),
2306 rng.r#gen(),
2307 rng.r#gen(),
2308 rng.r#gen(),
2309 ))
2310 .add_output(Output::contract(1, rng.r#gen(), rng.r#gen()))
2311 .add_output(Output::coin(rng.r#gen(), output_amount, asset))
2312 .add_output(Output::change(rng.r#gen(), 0, asset))
2313 .finalize()
2314 }
2315
2316 fn predicate_tx(
2318 rng: &mut StdRng,
2319 gas_limit: u64,
2320 witness_limit: u64,
2321 fee_input_amount: u64,
2322 predicate_gas_used: u64,
2323 ) -> Script {
2324 let asset = AssetId::default();
2325 let predicate = vec![op::ret(1)].into_iter().collect::<Vec<u8>>();
2326 let owner = Input::predicate_owner(&predicate);
2327 let zero_fee_limit = 0;
2328 TransactionBuilder::script(vec![], vec![])
2329 .max_fee_limit(zero_fee_limit)
2330 .script_gas_limit(gas_limit)
2331 .witness_limit(witness_limit)
2332 .add_input(Input::coin_predicate(
2333 rng.r#gen(),
2334 owner,
2335 fee_input_amount,
2336 asset,
2337 rng.r#gen(),
2338 predicate_gas_used,
2339 predicate,
2340 vec![],
2341 ))
2342 .add_output(Output::change(rng.r#gen(), 0, asset))
2343 .finalize()
2344 }
2345
2346 fn signed_message_coin_tx(
2348 rng: &mut StdRng,
2349 gas_limit: u64,
2350 input_amount: u64,
2351 max_fee: u64,
2352 ) -> Script {
2353 TransactionBuilder::script(vec![], vec![])
2354 .max_fee_limit(max_fee)
2355 .script_gas_limit(gas_limit)
2356 .add_unsigned_message_input(
2357 SecretKey::random(rng),
2358 rng.r#gen(),
2359 rng.r#gen(),
2360 input_amount,
2361 vec![],
2362 )
2363 .finalize()
2364 }
2365
2366 fn predicate_message_coin_tx(
2367 rng: &mut StdRng,
2368 gas_limit: u64,
2369 input_amount: u64,
2370 tip: u64,
2371 ) -> Script {
2372 TransactionBuilder::script(vec![], vec![])
2373 .tip(tip)
2374 .script_gas_limit(gas_limit)
2375 .add_input(Input::message_coin_predicate(
2376 rng.r#gen(),
2377 rng.r#gen(),
2378 input_amount,
2379 rng.r#gen(),
2380 Default::default(),
2381 vec![],
2382 vec![],
2383 ))
2384 .finalize()
2385 }
2386
2387 fn base_asset_tx(
2388 rng: &mut StdRng,
2389 input_amount: u64,
2390 gas_limit: u64,
2391 max_fee: u64,
2392 ) -> Script {
2393 base_asset_tx_with_tip(rng, input_amount, gas_limit, max_fee, None)
2394 }
2395
2396 fn base_asset_tx_with_tip(
2397 rng: &mut StdRng,
2398 input_amount: u64,
2399 gas_limit: u64,
2400 max_fee: u64,
2401 tip: Option<u64>,
2402 ) -> Script {
2403 let mut builder = TransactionBuilder::script(vec![], vec![]);
2404 if let Some(tip) = tip {
2405 builder.tip(tip);
2406 }
2407 builder
2408 .max_fee_limit(max_fee)
2409 .script_gas_limit(gas_limit)
2410 .add_unsigned_coin_input(
2411 SecretKey::random(rng),
2412 rng.r#gen(),
2413 input_amount,
2414 AssetId::default(),
2415 rng.r#gen(),
2416 )
2417 .add_output(Output::change(rng.r#gen(), 0, AssetId::default()))
2418 .finalize()
2419 }
2420}