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 tx_offset: usize,
414 pub fee_params: FeeParameters,
416 pub base_asset_id: AssetId,
418}
419
420#[cfg(feature = "test-helpers")]
421impl Default for CheckPredicateParams {
422 fn default() -> Self {
423 CheckPredicateParams::from(&ConsensusParameters::standard())
424 }
425}
426
427impl From<ConsensusParameters> for CheckPredicateParams {
428 fn from(value: ConsensusParameters) -> Self {
429 CheckPredicateParams::from(&value)
430 }
431}
432
433impl From<&ConsensusParameters> for CheckPredicateParams {
434 fn from(value: &ConsensusParameters) -> Self {
435 CheckPredicateParams {
436 gas_costs: value.gas_costs().clone(),
437 chain_id: value.chain_id(),
438 max_gas_per_predicate: value.predicate_params().max_gas_per_predicate(),
439 max_gas_per_tx: value.tx_params().max_gas_per_tx(),
440 max_inputs: value.tx_params().max_inputs(),
441 contract_max_size: value.contract_params().contract_max_size(),
442 max_message_data_length: value.predicate_params().max_message_data_length(),
443 tx_offset: value.tx_params().tx_offset(),
444 fee_params: *(value.fee_params()),
445 base_asset_id: *value.base_asset_id(),
446 }
447 }
448}
449
450#[async_trait::async_trait]
452pub trait CheckPredicates: Sized {
453 fn check_predicates(
455 self,
456 params: &CheckPredicateParams,
457 memory: impl Memory,
458 storage: &impl PredicateStorageRequirements,
459 ecal_handler: impl EcalHandler,
460 ) -> Result<Self, CheckError>;
461
462 async fn check_predicates_async<
464 Ecal: EcalHandler + Send + 'static,
465 E: ParallelExecutor,
466 >(
467 self,
468 params: &CheckPredicateParams,
469 pool: &impl VmMemoryPool,
470 storage: &impl PredicateStorageProvider,
471 ecal_handler: Ecal,
472 ) -> Result<Self, CheckError>;
473}
474
475#[async_trait::async_trait]
477pub trait EstimatePredicates: Sized {
478 fn estimate_predicates(
480 &mut self,
481 params: &CheckPredicateParams,
482 memory: impl Memory,
483 storage: &impl PredicateStorageRequirements,
484 ) -> Result<(), CheckError> {
485 Self::estimate_predicates_ecal(self, params, memory, storage, NotSupportedEcal)
486 }
487
488 async fn estimate_predicates_async<E: ParallelExecutor>(
490 &mut self,
491 params: &CheckPredicateParams,
492 pool: &impl VmMemoryPool,
493 storage: &impl PredicateStorageProvider,
494 ) -> Result<(), CheckError> {
495 Self::estimate_predicates_async_ecal::<NotSupportedEcal, E>(
496 self,
497 params,
498 pool,
499 storage,
500 NotSupportedEcal,
501 )
502 .await
503 }
504
505 fn estimate_predicates_ecal(
507 &mut self,
508 params: &CheckPredicateParams,
509 memory: impl Memory,
510 storage: &impl PredicateStorageRequirements,
511 ecal_handler: impl EcalHandler,
512 ) -> Result<(), CheckError>;
513
514 async fn estimate_predicates_async_ecal<
516 Ecal: EcalHandler + Send + 'static,
517 E: ParallelExecutor,
518 >(
519 &mut self,
520 params: &CheckPredicateParams,
521 pool: &impl VmMemoryPool,
522 storage: &impl PredicateStorageProvider,
523 ecal_handler: Ecal,
524 ) -> Result<(), CheckError>;
525}
526
527#[async_trait::async_trait]
529pub trait ParallelExecutor {
530 type Task: Future + Send + 'static;
532
533 fn create_task<F>(func: F) -> Self::Task
535 where
536 F: FnOnce() -> (usize, Result<Word, PredicateVerificationFailed>)
537 + Send
538 + 'static;
539
540 async fn execute_tasks(
542 futures: Vec<Self::Task>,
543 ) -> Vec<(usize, Result<Word, PredicateVerificationFailed>)>;
544}
545
546#[async_trait::async_trait]
547impl<Tx> CheckPredicates for Checked<Tx>
548where
549 Tx: ExecutableTransaction + Send + Sync + 'static,
550 <Tx as IntoChecked>::Metadata: crate::interpreter::CheckedMetadata + Send + Sync,
551{
552 fn check_predicates(
553 mut self,
554 params: &CheckPredicateParams,
555 memory: impl Memory,
556 storage: &impl PredicateStorageRequirements,
557 ecal_handler: impl EcalHandler,
558 ) -> Result<Self, CheckError> {
559 if !self.checks_bitmask.contains(Checks::Predicates) {
560 predicates::check_predicates(&self, params, memory, storage, ecal_handler)?;
561 self.checks_bitmask.insert(Checks::Predicates);
562 }
563 Ok(self)
564 }
565
566 async fn check_predicates_async<Ecal, E>(
567 mut self,
568 params: &CheckPredicateParams,
569 pool: &impl VmMemoryPool,
570 storage: &impl PredicateStorageProvider,
571 ecal_handler: Ecal,
572 ) -> Result<Self, CheckError>
573 where
574 Ecal: EcalHandler + Send + 'static,
575 E: ParallelExecutor,
576 {
577 if !self.checks_bitmask.contains(Checks::Predicates) {
578 predicates::check_predicates_async::<Tx, Ecal, E>(
579 &self,
580 params,
581 pool,
582 storage,
583 ecal_handler,
584 )
585 .await?;
586
587 self.checks_bitmask.insert(Checks::Predicates);
588
589 Ok(self)
590 } else {
591 Ok(self)
592 }
593 }
594}
595
596#[async_trait::async_trait]
597impl<Tx: ExecutableTransaction + Send + Sync + 'static> EstimatePredicates for Tx {
598 fn estimate_predicates_ecal(
599 &mut self,
600 params: &CheckPredicateParams,
601 memory: impl Memory,
602 storage: &impl PredicateStorageRequirements,
603 ecal_handler: impl EcalHandler,
604 ) -> Result<(), CheckError> {
605 predicates::estimate_predicates::<Self>(
606 self,
607 params,
608 memory,
609 storage,
610 ecal_handler,
611 )?;
612 Ok(())
613 }
614
615 async fn estimate_predicates_async_ecal<Ecal, E>(
616 &mut self,
617 params: &CheckPredicateParams,
618 pool: &impl VmMemoryPool,
619 storage: &impl PredicateStorageProvider,
620 ecal_handler: Ecal,
621 ) -> Result<(), CheckError>
622 where
623 E: ParallelExecutor,
624 Ecal: EcalHandler + Send + 'static,
625 {
626 predicates::estimate_predicates_async::<Self, Ecal, E>(
627 self,
628 params,
629 pool,
630 storage,
631 ecal_handler,
632 )
633 .await?;
634
635 Ok(())
636 }
637}
638
639#[async_trait::async_trait]
640impl EstimatePredicates for Transaction {
641 fn estimate_predicates_ecal(
642 &mut self,
643 params: &CheckPredicateParams,
644 memory: impl Memory,
645 storage: &impl PredicateStorageRequirements,
646 ecal_handler: impl EcalHandler,
647 ) -> Result<(), CheckError> {
648 match self {
649 Self::Script(tx) => {
650 tx.estimate_predicates_ecal(params, memory, storage, ecal_handler)
651 }
652 Self::Create(tx) => {
653 tx.estimate_predicates_ecal(params, memory, storage, ecal_handler)
654 }
655 Self::Mint(_) => Ok(()),
656 Self::Upgrade(tx) => {
657 tx.estimate_predicates_ecal(params, memory, storage, ecal_handler)
658 }
659 Self::Upload(tx) => {
660 tx.estimate_predicates_ecal(params, memory, storage, ecal_handler)
661 }
662 Self::Blob(tx) => {
663 tx.estimate_predicates_ecal(params, memory, storage, ecal_handler)
664 }
665 }
666 }
667
668 async fn estimate_predicates_async_ecal<
669 Ecal: EcalHandler + Send + 'static,
670 E: ParallelExecutor,
671 >(
672 &mut self,
673 params: &CheckPredicateParams,
674 pool: &impl VmMemoryPool,
675 storage: &impl PredicateStorageProvider,
676 ecal_handler: Ecal,
677 ) -> Result<(), CheckError> {
678 match self {
679 Self::Script(tx) => {
680 tx.estimate_predicates_async_ecal::<Ecal, E>(
681 params,
682 pool,
683 storage,
684 ecal_handler,
685 )
686 .await
687 }
688 Self::Create(tx) => {
689 tx.estimate_predicates_async_ecal::<Ecal, E>(
690 params,
691 pool,
692 storage,
693 ecal_handler,
694 )
695 .await
696 }
697 Self::Mint(_) => Ok(()),
698 Self::Upgrade(tx) => {
699 tx.estimate_predicates_async_ecal::<Ecal, E>(
700 params,
701 pool,
702 storage,
703 ecal_handler,
704 )
705 .await
706 }
707 Self::Upload(tx) => {
708 tx.estimate_predicates_async_ecal::<Ecal, E>(
709 params,
710 pool,
711 storage,
712 ecal_handler,
713 )
714 .await
715 }
716 Self::Blob(tx) => {
717 tx.estimate_predicates_async_ecal::<Ecal, E>(
718 params,
719 pool,
720 storage,
721 ecal_handler,
722 )
723 .await
724 }
725 }
726 }
727}
728
729#[async_trait::async_trait]
730impl CheckPredicates for Checked<Mint> {
731 fn check_predicates(
732 mut self,
733 _params: &CheckPredicateParams,
734 _memory: impl Memory,
735 _storage: &impl PredicateStorageRequirements,
736 _ecal_handler: impl EcalHandler,
737 ) -> Result<Self, CheckError> {
738 self.checks_bitmask.insert(Checks::Predicates);
739 Ok(self)
740 }
741
742 async fn check_predicates_async<
743 Ecal: EcalHandler + Send + 'static,
744 E: ParallelExecutor,
745 >(
746 mut self,
747 _params: &CheckPredicateParams,
748 _pool: &impl VmMemoryPool,
749 _storage: &impl PredicateStorageProvider,
750 _ecal_handler: Ecal,
751 ) -> Result<Self, CheckError> {
752 self.checks_bitmask.insert(Checks::Predicates);
753 Ok(self)
754 }
755}
756
757#[async_trait::async_trait]
758impl CheckPredicates for Checked<Transaction> {
759 fn check_predicates(
760 self,
761 params: &CheckPredicateParams,
762 memory: impl Memory,
763 storage: &impl PredicateStorageRequirements,
764 ecal_handler: impl EcalHandler,
765 ) -> Result<Self, CheckError> {
766 let checked_transaction: CheckedTransaction = self.into();
767 let checked_transaction: CheckedTransaction = match checked_transaction {
768 CheckedTransaction::Script(tx) => CheckPredicates::check_predicates(
769 tx,
770 params,
771 memory,
772 storage,
773 ecal_handler,
774 )?
775 .into(),
776 CheckedTransaction::Create(tx) => CheckPredicates::check_predicates(
777 tx,
778 params,
779 memory,
780 storage,
781 ecal_handler,
782 )?
783 .into(),
784 CheckedTransaction::Mint(tx) => CheckPredicates::check_predicates(
785 tx,
786 params,
787 memory,
788 storage,
789 ecal_handler,
790 )?
791 .into(),
792 CheckedTransaction::Upgrade(tx) => CheckPredicates::check_predicates(
793 tx,
794 params,
795 memory,
796 storage,
797 ecal_handler,
798 )?
799 .into(),
800 CheckedTransaction::Upload(tx) => CheckPredicates::check_predicates(
801 tx,
802 params,
803 memory,
804 storage,
805 ecal_handler,
806 )?
807 .into(),
808 CheckedTransaction::Blob(tx) => CheckPredicates::check_predicates(
809 tx,
810 params,
811 memory,
812 storage,
813 ecal_handler,
814 )?
815 .into(),
816 };
817 Ok(checked_transaction.into())
818 }
819
820 async fn check_predicates_async<Ecal, E>(
821 mut self,
822 params: &CheckPredicateParams,
823 pool: &impl VmMemoryPool,
824 storage: &impl PredicateStorageProvider,
825 ecal_handler: Ecal,
826 ) -> Result<Self, CheckError>
827 where
828 Ecal: EcalHandler + Send + 'static,
829 E: ParallelExecutor,
830 {
831 let checked_transaction: CheckedTransaction = self.into();
832
833 let checked_transaction: CheckedTransaction = match checked_transaction {
834 CheckedTransaction::Script(tx) => CheckPredicates::check_predicates_async::<
835 Ecal,
836 E,
837 >(
838 tx, params, pool, storage, ecal_handler
839 )
840 .await?
841 .into(),
842 CheckedTransaction::Create(tx) => CheckPredicates::check_predicates_async::<
843 Ecal,
844 E,
845 >(
846 tx, params, pool, storage, ecal_handler
847 )
848 .await?
849 .into(),
850 CheckedTransaction::Mint(tx) => CheckPredicates::check_predicates_async::<
851 Ecal,
852 E,
853 >(
854 tx, params, pool, storage, ecal_handler
855 )
856 .await?
857 .into(),
858 CheckedTransaction::Upgrade(tx) => CheckPredicates::check_predicates_async::<
859 Ecal,
860 E,
861 >(
862 tx, params, pool, storage, ecal_handler
863 )
864 .await?
865 .into(),
866 CheckedTransaction::Upload(tx) => CheckPredicates::check_predicates_async::<
867 Ecal,
868 E,
869 >(
870 tx, params, pool, storage, ecal_handler
871 )
872 .await?
873 .into(),
874 CheckedTransaction::Blob(tx) => CheckPredicates::check_predicates_async::<
875 Ecal,
876 E,
877 >(
878 tx, params, pool, storage, ecal_handler
879 )
880 .await?
881 .into(),
882 };
883
884 Ok(checked_transaction.into())
885 }
886}
887
888#[derive(Debug, Clone, Eq, PartialEq, Hash)]
894#[allow(missing_docs)]
895pub enum CheckedTransaction {
896 Script(Checked<Script>),
897 Create(Checked<Create>),
898 Mint(Checked<Mint>),
899 Upgrade(Checked<Upgrade>),
900 Upload(Checked<Upload>),
901 Blob(Checked<Blob>),
902}
903
904impl From<Checked<Transaction>> for CheckedTransaction {
905 fn from(checked: Checked<Transaction>) -> Self {
906 let Checked {
907 transaction,
908 metadata,
909 checks_bitmask,
910 } = checked;
911
912 match (transaction, metadata) {
914 (Transaction::Script(transaction), CheckedMetadata::Script(metadata)) => {
915 Self::Script(Checked::new(transaction, metadata, checks_bitmask))
916 }
917 (Transaction::Create(transaction), CheckedMetadata::Create(metadata)) => {
918 Self::Create(Checked::new(transaction, metadata, checks_bitmask))
919 }
920 (Transaction::Mint(transaction), CheckedMetadata::Mint(metadata)) => {
921 Self::Mint(Checked::new(transaction, metadata, checks_bitmask))
922 }
923 (Transaction::Upgrade(transaction), CheckedMetadata::Upgrade(metadata)) => {
924 Self::Upgrade(Checked::new(transaction, metadata, checks_bitmask))
925 }
926 (Transaction::Upload(transaction), CheckedMetadata::Upload(metadata)) => {
927 Self::Upload(Checked::new(transaction, metadata, checks_bitmask))
928 }
929 (Transaction::Blob(transaction), CheckedMetadata::Blob(metadata)) => {
930 Self::Blob(Checked::new(transaction, metadata, checks_bitmask))
931 }
932 (Transaction::Script(_), _) => unreachable!(),
937 (Transaction::Create(_), _) => unreachable!(),
938 (Transaction::Mint(_), _) => unreachable!(),
939 (Transaction::Upgrade(_), _) => unreachable!(),
940 (Transaction::Upload(_), _) => unreachable!(),
941 (Transaction::Blob(_), _) => unreachable!(),
942 }
943 }
944}
945
946impl From<Checked<Script>> for CheckedTransaction {
947 fn from(checked: Checked<Script>) -> Self {
948 Self::Script(checked)
949 }
950}
951
952impl From<Checked<Create>> for CheckedTransaction {
953 fn from(checked: Checked<Create>) -> Self {
954 Self::Create(checked)
955 }
956}
957
958impl From<Checked<Mint>> for CheckedTransaction {
959 fn from(checked: Checked<Mint>) -> Self {
960 Self::Mint(checked)
961 }
962}
963
964impl From<Checked<Upgrade>> for CheckedTransaction {
965 fn from(checked: Checked<Upgrade>) -> Self {
966 Self::Upgrade(checked)
967 }
968}
969
970impl From<Checked<Upload>> for CheckedTransaction {
971 fn from(checked: Checked<Upload>) -> Self {
972 Self::Upload(checked)
973 }
974}
975
976impl From<Checked<Blob>> for CheckedTransaction {
977 fn from(checked: Checked<Blob>) -> Self {
978 Self::Blob(checked)
979 }
980}
981
982impl From<CheckedTransaction> for Checked<Transaction> {
983 fn from(checked: CheckedTransaction) -> Self {
984 match checked {
985 CheckedTransaction::Script(Checked {
986 transaction,
987 metadata,
988 checks_bitmask,
989 }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
990 CheckedTransaction::Create(Checked {
991 transaction,
992 metadata,
993 checks_bitmask,
994 }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
995 CheckedTransaction::Mint(Checked {
996 transaction,
997 metadata,
998 checks_bitmask,
999 }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
1000 CheckedTransaction::Upgrade(Checked {
1001 transaction,
1002 metadata,
1003 checks_bitmask,
1004 }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
1005 CheckedTransaction::Upload(Checked {
1006 transaction,
1007 metadata,
1008 checks_bitmask,
1009 }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
1010 CheckedTransaction::Blob(Checked {
1011 transaction,
1012 metadata,
1013 checks_bitmask,
1014 }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
1015 }
1016 }
1017}
1018
1019#[derive(Debug, Clone, Eq, PartialEq, Hash)]
1021#[allow(missing_docs)]
1022pub enum CheckedMetadata {
1023 Script(<Script as IntoChecked>::Metadata),
1024 Create(<Create as IntoChecked>::Metadata),
1025 Mint(<Mint as IntoChecked>::Metadata),
1026 Upgrade(<Upgrade as IntoChecked>::Metadata),
1027 Upload(<Upload as IntoChecked>::Metadata),
1028 Blob(<Blob as IntoChecked>::Metadata),
1029}
1030
1031impl From<<Script as IntoChecked>::Metadata> for CheckedMetadata {
1032 fn from(metadata: <Script as IntoChecked>::Metadata) -> Self {
1033 Self::Script(metadata)
1034 }
1035}
1036
1037impl From<<Create as IntoChecked>::Metadata> for CheckedMetadata {
1038 fn from(metadata: <Create as IntoChecked>::Metadata) -> Self {
1039 Self::Create(metadata)
1040 }
1041}
1042
1043impl From<<Mint as IntoChecked>::Metadata> for CheckedMetadata {
1044 fn from(metadata: <Mint as IntoChecked>::Metadata) -> Self {
1045 Self::Mint(metadata)
1046 }
1047}
1048
1049impl From<<Upgrade as IntoChecked>::Metadata> for CheckedMetadata {
1050 fn from(metadata: <Upgrade as IntoChecked>::Metadata) -> Self {
1051 Self::Upgrade(metadata)
1052 }
1053}
1054
1055impl From<<Upload as IntoChecked>::Metadata> for CheckedMetadata {
1056 fn from(metadata: <Upload as IntoChecked>::Metadata) -> Self {
1057 Self::Upload(metadata)
1058 }
1059}
1060impl From<<Blob as IntoChecked>::Metadata> for CheckedMetadata {
1061 fn from(metadata: <Blob as IntoChecked>::Metadata) -> Self {
1062 Self::Blob(metadata)
1063 }
1064}
1065
1066impl IntoChecked for Transaction {
1067 type Metadata = CheckedMetadata;
1068
1069 fn into_checked_basic(
1070 self,
1071 block_height: BlockHeight,
1072 consensus_params: &ConsensusParameters,
1073 ) -> Result<Checked<Self>, CheckError> {
1074 match self {
1075 Self::Script(tx) => {
1076 let (transaction, metadata) = tx
1077 .into_checked_basic(block_height, consensus_params)?
1078 .into();
1079 Ok((transaction.into(), metadata.into()))
1080 }
1081 Self::Create(tx) => {
1082 let (transaction, metadata) = tx
1083 .into_checked_basic(block_height, consensus_params)?
1084 .into();
1085 Ok((transaction.into(), metadata.into()))
1086 }
1087 Self::Mint(tx) => {
1088 let (transaction, metadata) = tx
1089 .into_checked_basic(block_height, consensus_params)?
1090 .into();
1091 Ok((transaction.into(), metadata.into()))
1092 }
1093 Self::Upgrade(tx) => {
1094 let (transaction, metadata) = tx
1095 .into_checked_basic(block_height, consensus_params)?
1096 .into();
1097 Ok((transaction.into(), metadata.into()))
1098 }
1099 Self::Upload(tx) => {
1100 let (transaction, metadata) = tx
1101 .into_checked_basic(block_height, consensus_params)?
1102 .into();
1103 Ok((transaction.into(), metadata.into()))
1104 }
1105 Self::Blob(tx) => {
1106 let (transaction, metadata) = tx
1107 .into_checked_basic(block_height, consensus_params)?
1108 .into();
1109 Ok((transaction.into(), metadata.into()))
1110 }
1111 }
1112 .map(|(transaction, metadata)| Checked::basic(transaction, metadata))
1113 }
1114}
1115
1116impl From<ValidityError> for CheckError {
1117 fn from(value: ValidityError) -> Self {
1118 CheckError::Validity(value)
1119 }
1120}
1121
1122impl From<PredicateVerificationFailed> for CheckError {
1123 fn from(value: PredicateVerificationFailed) -> Self {
1124 CheckError::PredicateVerificationFailed(value)
1125 }
1126}
1127
1128#[cfg(feature = "random")]
1129#[allow(non_snake_case)]
1130#[allow(clippy::arithmetic_side_effects, clippy::cast_possible_truncation)]
1131#[cfg(test)]
1132mod tests {
1133
1134 use super::*;
1135 use alloc::vec;
1136 use fuel_asm::op;
1137 use fuel_crypto::SecretKey;
1138 use fuel_tx::{
1139 Script,
1140 TransactionBuilder,
1141 ValidityError,
1142 field::{
1143 ScriptGasLimit,
1144 Tip,
1145 WitnessLimit,
1146 Witnesses,
1147 },
1148 };
1149 use fuel_types::canonical::Serialize;
1150 use quickcheck::TestResult;
1151 use quickcheck_macros::quickcheck;
1152 use rand::{
1153 Rng,
1154 SeedableRng,
1155 rngs::StdRng,
1156 };
1157
1158 fn params(factor: u64) -> ConsensusParameters {
1159 ConsensusParameters::new(
1160 TxParameters::default(),
1161 PredicateParameters::default(),
1162 ScriptParameters::default(),
1163 ContractParameters::default(),
1164 FeeParameters::default().with_gas_price_factor(factor),
1165 Default::default(),
1166 Default::default(),
1167 Default::default(),
1168 Default::default(),
1169 Default::default(),
1170 Default::default(),
1171 )
1172 }
1173
1174 #[test]
1175 fn into_checked__tx_accepts_valid_tx() {
1176 let rng = &mut StdRng::seed_from_u64(2322u64);
1178 let gas_limit = 1000;
1179 let input_amount = 1000;
1180 let output_amount = 10;
1181 let max_fee_limit = 500;
1182 let tx =
1183 valid_coin_tx(rng, gas_limit, input_amount, output_amount, max_fee_limit);
1184
1185 let checked = tx
1186 .clone()
1187 .into_checked(Default::default(), &ConsensusParameters::standard())
1188 .expect("Expected valid transaction");
1189
1190 assert_eq!(checked.transaction(), &tx);
1192 assert_eq!(
1194 checked.metadata().non_retryable_balances[&AssetId::default()],
1195 input_amount - max_fee_limit - output_amount
1196 );
1197 }
1198
1199 #[test]
1200 fn into_checked__tx_accepts_valid_signed_message_coin_for_fees() {
1201 let rng = &mut StdRng::seed_from_u64(2322u64);
1203 let input_amount = 1000;
1204 let gas_limit = 1000;
1205 let zero_fee_limit = 500;
1206 let tx = signed_message_coin_tx(rng, gas_limit, input_amount, zero_fee_limit);
1207
1208 let checked = tx
1209 .into_checked(Default::default(), &ConsensusParameters::standard())
1210 .expect("Expected valid transaction");
1211
1212 assert_eq!(
1214 checked.metadata().non_retryable_balances[&AssetId::default()],
1215 input_amount - checked.transaction.max_fee_limit()
1216 );
1217 }
1218
1219 #[test]
1220 fn into_checked__tx_excludes_message_output_amount_from_fee() {
1221 let rng = &mut StdRng::seed_from_u64(2322u64);
1223 let input_amount = 100;
1224 let gas_limit = 1000;
1225 let zero_fee_limit = 50;
1226 let tx = signed_message_coin_tx(rng, gas_limit, input_amount, zero_fee_limit);
1227
1228 let checked = tx
1229 .into_checked(Default::default(), &ConsensusParameters::standard())
1230 .expect("Expected valid transaction");
1231
1232 assert_eq!(
1234 checked.metadata().non_retryable_balances[&AssetId::default()],
1235 input_amount - checked.transaction.max_fee_limit()
1236 );
1237 }
1238
1239 #[test]
1240 fn into_checked__message_data_signed_message_is_not_used_to_cover_fees() {
1241 let rng = &mut StdRng::seed_from_u64(2322u64);
1242
1243 let input_amount = 100;
1245
1246 let max_fee = input_amount;
1248 let tx = TransactionBuilder::script(vec![], vec![])
1249 .max_fee_limit(max_fee)
1250 .add_unsigned_message_input(SecretKey::random(rng), rng.r#gen(), rng.r#gen(), input_amount, vec![0xff; 10])
1252 .add_unsigned_coin_input(SecretKey::random(rng), rng.r#gen(), 0, AssetId::BASE, rng.r#gen())
1254 .finalize();
1255
1256 let err = tx
1257 .into_checked(Default::default(), &ConsensusParameters::standard())
1258 .expect_err("Expected valid transaction");
1259
1260 assert!(matches!(
1262 err,
1263 CheckError::Validity(ValidityError::InsufficientFeeAmount {
1264 expected: _,
1265 provided: 0
1266 })
1267 ));
1268 }
1269
1270 #[test]
1271 fn message_data_predicate_message_is_not_used_to_cover_fees() {
1272 let rng = &mut StdRng::seed_from_u64(2322u64);
1273 let gas_limit = 1000;
1274
1275 let input_amount = 100;
1277
1278 let max_fee = input_amount;
1280
1281 let tx = TransactionBuilder::script(vec![], vec![])
1282 .max_fee_limit(max_fee)
1283 .script_gas_limit(gas_limit)
1284 .add_input(Input::message_data_predicate(
1285 rng.r#gen(),
1286 rng.r#gen(),
1287 input_amount,
1288 rng.r#gen(),
1289 Default::default(),
1290 vec![0xff; 10],
1291 vec![0xaa; 10],
1292 vec![0xbb; 10],
1293 ))
1294 .add_unsigned_coin_input(SecretKey::random(rng), rng.r#gen(), 0, AssetId::BASE, rng.r#gen())
1296 .finalize();
1297
1298 let err = tx
1299 .into_checked(Default::default(), &ConsensusParameters::standard())
1300 .expect_err("Expected valid transaction");
1301
1302 assert!(matches!(
1304 err,
1305 CheckError::Validity(ValidityError::InsufficientFeeAmount {
1306 expected: _,
1307 provided: 0
1308 })
1309 ));
1310 }
1311
1312 #[quickcheck]
1315 fn max_fee_coin_input(
1316 gas_price: u64,
1317 gas_limit: u64,
1318 witness_limit: u64,
1319 input_amount: u64,
1320 gas_price_factor: u64,
1321 seed: u64,
1322 ) -> TestResult {
1323 if gas_price_factor == 0 {
1327 return TestResult::discard();
1328 }
1329
1330 let rng = &mut StdRng::seed_from_u64(seed);
1331 let gas_costs = GasCosts::default();
1332 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(gas_price_factor);
1333 let predicate_gas_used = rng.r#gen();
1334 let tx = predicate_tx(
1335 rng,
1336 gas_limit,
1337 witness_limit,
1338 input_amount,
1339 predicate_gas_used,
1340 );
1341
1342 if let Ok(valid) = is_valid_max_fee(&tx, gas_price, &gas_costs, &fee_params) {
1343 TestResult::from_bool(valid)
1344 } else {
1345 TestResult::discard()
1346 }
1347 }
1348
1349 #[quickcheck]
1352 fn min_fee_coin_input(
1353 gas_price: u64,
1354 gas_limit: u64,
1355 witness_limit: u64,
1356 input_amount: u64,
1357 gas_price_factor: u64,
1358 seed: u64,
1359 ) -> TestResult {
1360 if gas_price_factor == 0 {
1364 return TestResult::discard();
1365 }
1366 let rng = &mut StdRng::seed_from_u64(seed);
1367 let gas_costs = GasCosts::default();
1368 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(gas_price_factor);
1369 let predicate_gas_used = rng.r#gen();
1370 let tx = predicate_tx(
1371 rng,
1372 gas_limit,
1373 witness_limit,
1374 input_amount,
1375 predicate_gas_used,
1376 );
1377
1378 if let Ok(valid) = is_valid_max_fee(&tx, gas_price, &gas_costs, &fee_params) {
1379 TestResult::from_bool(valid)
1380 } else {
1381 TestResult::discard()
1382 }
1383 }
1384
1385 #[quickcheck]
1388 fn max_fee_message_input(
1389 gas_price: u64,
1390 gas_limit: u64,
1391 input_amount: u64,
1392 gas_price_factor: u64,
1393 tip: u64,
1394 seed: u64,
1395 ) -> TestResult {
1396 if gas_price_factor == 0 {
1398 return TestResult::discard();
1399 }
1400
1401 let rng = &mut StdRng::seed_from_u64(seed);
1402 let gas_costs = GasCosts::default();
1403 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(gas_price_factor);
1404 let tx = predicate_message_coin_tx(rng, gas_limit, input_amount, tip);
1405
1406 if let Ok(valid) = is_valid_max_fee(&tx, gas_price, &gas_costs, &fee_params) {
1407 TestResult::from_bool(valid)
1408 } else {
1409 TestResult::discard()
1410 }
1411 }
1412
1413 #[quickcheck]
1415 fn refund_when_used_gas_is_zero(
1416 gas_price: u64,
1417 gas_limit: u64,
1418 input_amount: u64,
1419 gas_price_factor: u64,
1420 seed: u64,
1421 tip: u64,
1422 ) -> TestResult {
1423 if gas_price_factor == 0 {
1425 return TestResult::discard();
1426 }
1427
1428 let rng = &mut StdRng::seed_from_u64(seed);
1429 let gas_costs = GasCosts::default();
1430 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(gas_price_factor);
1431 let tx = predicate_message_coin_tx(rng, gas_limit, input_amount, tip);
1432
1433 let used_gas = 0;
1435
1436 let refund = tx.refund_fee(&gas_costs, &fee_params, used_gas, gas_price);
1438
1439 let min_fee = tx.min_fee(&gas_costs, &fee_params, gas_price);
1440 let max_fee = tx.max_fee(&gas_costs, &fee_params, gas_price);
1441
1442 if let Some(refund) = refund {
1444 TestResult::from_bool(max_fee - min_fee == refund as u128)
1445 } else {
1446 TestResult::discard()
1447 }
1448 }
1449
1450 #[quickcheck]
1453 fn min_fee_message_input(
1454 gas_limit: u64,
1455 input_amount: u64,
1456 gas_price: u64,
1457 gas_price_factor: u64,
1458 tip: u64,
1459 seed: u64,
1460 ) -> TestResult {
1461 if gas_price_factor == 0 {
1465 return TestResult::discard();
1466 }
1467 let rng = &mut StdRng::seed_from_u64(seed);
1468 let gas_costs = GasCosts::default();
1469 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(gas_price_factor);
1470 let tx = predicate_message_coin_tx(rng, gas_limit, input_amount, tip);
1471
1472 if let Ok(valid) = is_valid_min_fee(&tx, &gas_costs, &fee_params, gas_price) {
1473 TestResult::from_bool(valid)
1474 } else {
1475 TestResult::discard()
1476 }
1477 }
1478
1479 #[test]
1480 fn fee_multiple_signed_inputs() {
1481 let rng = &mut StdRng::seed_from_u64(2322u64);
1482 let gas_price = 100;
1483 let gas_limit = 1000;
1484 let gas_costs = GasCosts::default();
1485 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(1);
1486 let tx = TransactionBuilder::script(vec![], vec![])
1487 .script_gas_limit(gas_limit)
1488 .add_unsigned_message_input(
1490 SecretKey::random(rng),
1491 rng.r#gen(),
1492 rng.r#gen(),
1493 rng.r#gen::<u32>() as u64,
1494 vec![],
1495 )
1496 .add_unsigned_message_input(
1497 SecretKey::random(rng),
1498 rng.r#gen(),
1499 rng.r#gen(),
1500 rng.r#gen::<u32>() as u64,
1501 vec![],
1502 )
1503 .add_unsigned_message_input(
1504 SecretKey::random(rng),
1505 rng.r#gen(),
1506 rng.r#gen(),
1507 rng.r#gen::<u32>() as u64,
1508 vec![],
1509 )
1510 .finalize();
1511 let fee =
1512 TransactionFee::checked_from_tx(&gas_costs, &fee_params, &tx, gas_price)
1513 .unwrap();
1514
1515 let min_fee = fee.min_fee();
1516 let expected_min_fee = (tx.metered_bytes_size() as u64
1517 * fee_params.gas_per_byte()
1518 + gas_costs.vm_initialization().resolve(tx.size() as u64)
1519 + 3 * gas_costs.eck1()
1520 + gas_costs.s256().resolve(tx.size() as u64))
1521 * gas_price;
1522 assert_eq!(min_fee, expected_min_fee);
1523
1524 let max_fee = fee.max_fee();
1525 let expected_max_fee = expected_min_fee + gas_limit * gas_price;
1526 assert_eq!(max_fee, expected_max_fee);
1527 }
1528
1529 #[test]
1530 fn fee_multiple_signed_inputs_single_owner() {
1531 let rng = &mut StdRng::seed_from_u64(2322u64);
1532 let gas_price = 100;
1533 let gas_limit = 1000;
1534 let gas_costs = GasCosts::default();
1535 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(1);
1536 let secret = SecretKey::random(rng);
1537 let tx = TransactionBuilder::script(vec![], vec![])
1538 .script_gas_limit(gas_limit)
1539 .add_unsigned_message_input(
1541 secret,
1542 rng.r#gen(),
1543 rng.r#gen(),
1544 rng.r#gen::<u32>() as u64,
1545 vec![],
1546 )
1547 .add_unsigned_message_input(
1548 secret,
1549 rng.r#gen(),
1550 rng.r#gen(),
1551 rng.r#gen::<u32>() as u64,
1552 vec![],
1553 )
1554 .add_unsigned_message_input(
1555 secret,
1556 rng.r#gen(),
1557 rng.r#gen(),
1558 rng.r#gen::<u32>() as u64,
1559 vec![],
1560 )
1561 .finalize();
1562 let fee =
1563 TransactionFee::checked_from_tx(&gas_costs, &fee_params, &tx, gas_price)
1564 .unwrap();
1565
1566 let min_fee = fee.min_fee();
1567 let expected_min_fee = (tx.metered_bytes_size() as u64
1571 * fee_params.gas_per_byte()
1572 + gas_costs.vm_initialization().resolve(tx.size() as u64)
1573 + gas_costs.eck1()
1574 + gas_costs.s256().resolve(tx.size() as u64))
1575 * gas_price;
1576 assert_eq!(min_fee, expected_min_fee);
1577
1578 let max_fee = fee.max_fee();
1579 let expected_max_fee = min_fee + gas_limit * gas_price;
1580 assert_eq!(max_fee, expected_max_fee);
1581 }
1582
1583 fn random_bytes<const N: usize, R: Rng + ?Sized>(rng: &mut R) -> Box<[u8; N]> {
1584 let mut bytes = Box::new([0u8; N]);
1585 for chunk in bytes.chunks_mut(32) {
1586 rng.fill(chunk);
1587 }
1588 bytes
1589 }
1590
1591 #[test]
1592 fn min_fee_multiple_predicate_inputs() {
1593 let rng = &mut StdRng::seed_from_u64(2322u64);
1594 let gas_price = 100;
1595 let gas_limit = 1000;
1596 let gas_costs = GasCosts::default();
1597 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(1);
1598 let predicate_1 = random_bytes::<1024, _>(rng);
1599 let predicate_2 = random_bytes::<2048, _>(rng);
1600 let predicate_3 = random_bytes::<4096, _>(rng);
1601 let tx = TransactionBuilder::script(vec![], vec![])
1602 .script_gas_limit(gas_limit)
1603 .add_input(Input::message_coin_predicate(
1605 rng.r#gen(),
1606 rng.r#gen(),
1607 rng.r#gen(),
1608 rng.r#gen(),
1609 50,
1610 predicate_1.to_vec(),
1611 vec![],
1612 ))
1613 .add_input(Input::message_coin_predicate(
1614 rng.r#gen(),
1615 rng.r#gen(),
1616 rng.r#gen(),
1617 rng.r#gen(),
1618 100,
1619 predicate_2.to_vec(),
1620 vec![],
1621 ))
1622 .add_input(Input::message_coin_predicate(
1623 rng.r#gen(),
1624 rng.r#gen(),
1625 rng.r#gen(),
1626 rng.r#gen(),
1627 200,
1628 predicate_3.to_vec(),
1629 vec![],
1630 ))
1631 .finalize();
1632 let fee =
1633 TransactionFee::checked_from_tx(&gas_costs, &fee_params, &tx, gas_price)
1634 .unwrap();
1635
1636 let min_fee = fee.min_fee();
1637 let expected_min_fee = (tx.size() as u64 * fee_params.gas_per_byte()
1638 + gas_costs.vm_initialization().resolve(tx.size() as u64)
1639 + gas_costs.contract_root().resolve(predicate_1.len() as u64)
1640 + gas_costs.contract_root().resolve(predicate_2.len() as u64)
1641 + gas_costs.contract_root().resolve(predicate_3.len() as u64)
1642 + 3 * gas_costs.vm_initialization().resolve(tx.size() as u64)
1643 + 50
1644 + 100
1645 + 200
1646 + gas_costs.s256().resolve(tx.size() as u64))
1647 * gas_price;
1648 assert_eq!(min_fee, expected_min_fee);
1649
1650 let max_fee = fee.max_fee();
1651 let expected_max_fee = min_fee + gas_limit * gas_price;
1652 assert_eq!(max_fee, expected_max_fee);
1653 }
1654
1655 #[test]
1656 fn min_fee_multiple_signed_and_predicate_inputs() {
1657 let rng = &mut StdRng::seed_from_u64(2322u64);
1658 let gas_price = 100;
1659 let gas_limit = 1000;
1660 let gas_costs = GasCosts::default();
1661 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(1);
1662 let predicate_1 = random_bytes::<1024, _>(rng);
1663 let predicate_2 = random_bytes::<2048, _>(rng);
1664 let predicate_3 = random_bytes::<4096, _>(rng);
1665 let tx = TransactionBuilder::script(vec![], vec![])
1666 .script_gas_limit(gas_limit)
1667 .add_unsigned_message_input(
1669 SecretKey::random(rng),
1670 rng.r#gen(),
1671 rng.r#gen(),
1672 rng.r#gen::<u32>() as u64,
1673 vec![],
1674 )
1675 .add_unsigned_message_input(
1676 SecretKey::random(rng),
1677 rng.r#gen(),
1678 rng.r#gen(),
1679 rng.r#gen::<u32>() as u64,
1680 vec![],
1681 )
1682 .add_unsigned_message_input(
1683 SecretKey::random(rng),
1684 rng.r#gen(),
1685 rng.r#gen(),
1686 rng.r#gen::<u32>() as u64,
1687 vec![],
1688 )
1689 .add_input(Input::message_coin_predicate(
1691 rng.r#gen(),
1692 rng.r#gen(),
1693 rng.r#gen(),
1694 rng.r#gen(),
1695 50,
1696 predicate_1.to_vec(),
1697 vec![],
1698 ))
1699 .add_input(Input::message_coin_predicate(
1700 rng.r#gen(),
1701 rng.r#gen(),
1702 rng.r#gen(),
1703 rng.r#gen(),
1704 100,
1705 predicate_2.to_vec(),
1706 vec![],
1707 ))
1708 .add_input(Input::message_coin_predicate(
1709 rng.r#gen(),
1710 rng.r#gen(),
1711 rng.r#gen(),
1712 rng.r#gen(),
1713 200,
1714 predicate_3.to_vec(),
1715 vec![],
1716 ))
1717 .finalize();
1718 let fee =
1719 TransactionFee::checked_from_tx(&gas_costs, &fee_params, &tx, gas_price)
1720 .unwrap();
1721
1722 let min_fee = fee.min_fee();
1723 let expected_min_fee = (tx.metered_bytes_size() as u64
1724 * fee_params.gas_per_byte()
1725 + 3 * gas_costs.eck1()
1726 + gas_costs.vm_initialization().resolve(tx.size() as u64)
1727 + gas_costs.contract_root().resolve(predicate_1.len() as u64)
1728 + gas_costs.contract_root().resolve(predicate_2.len() as u64)
1729 + gas_costs.contract_root().resolve(predicate_3.len() as u64)
1730 + 3 * gas_costs.vm_initialization().resolve(tx.size() as u64)
1731 + 50
1732 + 100
1733 + 200
1734 + gas_costs.s256().resolve(tx.size() as u64))
1735 * gas_price;
1736 assert_eq!(min_fee, expected_min_fee);
1737
1738 let max_fee = fee.max_fee();
1739 let expected_max_fee = min_fee + gas_limit * gas_price;
1740 assert_eq!(max_fee, expected_max_fee);
1741 }
1742
1743 #[test]
1744 fn fee_create_tx() {
1745 let rng = &mut StdRng::seed_from_u64(2322u64);
1746 let gas_price = 100;
1747 let witness_limit = 1000;
1748 let gas_costs = GasCosts::default();
1749 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(1);
1750 let gen_storage_slot = || rng.r#gen::<StorageSlot>();
1751 let storage_slots = core::iter::repeat_with(gen_storage_slot)
1752 .take(100)
1753 .collect::<Vec<_>>();
1754 let storage_slots_len = storage_slots.len();
1755 let bytecode = rng.r#gen::<Witness>();
1756 let bytecode_len = bytecode.as_ref().len();
1757 let salt = rng.r#gen::<Salt>();
1758 let tx = TransactionBuilder::create(bytecode.clone(), salt, storage_slots)
1759 .witness_limit(witness_limit)
1760 .finalize();
1761 let fee =
1762 TransactionFee::checked_from_tx(&gas_costs, &fee_params, &tx, gas_price)
1763 .unwrap();
1764
1765 let min_fee = fee.min_fee();
1766 let expected_min_fee = (tx.metered_bytes_size() as u64
1767 * fee_params.gas_per_byte()
1768 + gas_costs.state_root().resolve(storage_slots_len as Word)
1769 + gas_costs.contract_root().resolve(bytecode_len as Word)
1770 + gas_costs.vm_initialization().resolve(tx.size() as u64)
1771 + gas_costs.s256().resolve(100)
1772 + gas_costs.s256().resolve(tx.size() as u64))
1773 * gas_price;
1774 assert_eq!(min_fee, expected_min_fee);
1775
1776 let max_fee = fee.max_fee();
1777 let expected_max_fee = min_fee
1778 + (witness_limit - bytecode.size() as u64)
1779 * fee_params.gas_per_byte()
1780 * gas_price;
1781 assert_eq!(max_fee, expected_max_fee);
1782 }
1783
1784 #[test]
1785 fn fee_create_tx_no_bytecode() {
1786 let rng = &mut StdRng::seed_from_u64(2322u64);
1787 let gas_price = 100;
1788 let witness_limit = 1000;
1789 let gas_costs = GasCosts::default();
1790 let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(1);
1791 let bytecode: Witness = Vec::<u8>::new().into();
1792 let salt = rng.r#gen::<Salt>();
1793 let tx = TransactionBuilder::create(bytecode.clone(), salt, vec![])
1794 .witness_limit(witness_limit)
1795 .finalize();
1796 let fee =
1797 TransactionFee::checked_from_tx(&gas_costs, &fee_params, &tx, gas_price)
1798 .unwrap();
1799
1800 let min_fee = fee.min_fee();
1801 let expected_min_fee = (tx.metered_bytes_size() as u64
1802 * fee_params.gas_per_byte()
1803 + gas_costs.state_root().resolve(0)
1804 + gas_costs.contract_root().resolve(0)
1805 + gas_costs.vm_initialization().resolve(tx.size() as u64)
1806 + gas_costs.s256().resolve(100)
1807 + gas_costs.s256().resolve(tx.size() as u64))
1808 * gas_price;
1809 assert_eq!(min_fee, expected_min_fee);
1810
1811 let max_fee = fee.max_fee();
1812 let expected_max_fee = min_fee
1813 + (witness_limit - bytecode.size_static() as u64)
1814 * fee_params.gas_per_byte()
1815 * gas_price;
1816 assert_eq!(max_fee, expected_max_fee);
1817 }
1818
1819 #[test]
1820 fn checked_tx_rejects_invalid_tx() {
1821 let rng = &mut StdRng::seed_from_u64(2322u64);
1823 let asset = rng.r#gen();
1824 let gas_limit = 100;
1825 let input_amount = 1_000;
1826
1827 let tx = TransactionBuilder::script(vec![], vec![])
1829 .script_gas_limit(gas_limit)
1830 .add_input(Input::coin_signed(
1831 rng.r#gen(),
1832 rng.r#gen(),
1833 input_amount,
1834 asset,
1835 rng.r#gen(),
1836 Default::default(),
1837 ))
1838 .add_input(Input::contract(
1839 rng.r#gen(),
1840 rng.r#gen(),
1841 rng.r#gen(),
1842 rng.r#gen(),
1843 rng.r#gen(),
1844 ))
1845 .add_output(Output::contract(1, rng.r#gen(), rng.r#gen()))
1846 .add_output(Output::coin(rng.r#gen(), 10, asset))
1847 .add_output(Output::change(rng.r#gen(), 0, asset))
1848 .add_witness(Default::default())
1849 .finalize();
1850
1851 let err = tx
1852 .into_checked(Default::default(), &ConsensusParameters::standard())
1853 .expect_err("Expected invalid transaction");
1854
1855 assert!(matches!(
1857 err,
1858 CheckError::Validity(ValidityError::InputInvalidSignature { .. })
1859 ));
1860 }
1861
1862 #[test]
1863 fn into_checked__tx_fails_when_provided_fees_dont_cover_byte_costs() {
1864 let rng = &mut StdRng::seed_from_u64(2322u64);
1865
1866 let arb_input_amount = 1;
1867 let gas_price = 2; let gas_limit = 0; let factor = 1;
1870 let zero_max_fee = 0;
1871 let params = params(factor);
1872
1873 let transaction = base_asset_tx(rng, arb_input_amount, gas_limit, zero_max_fee);
1875 transaction
1876 .clone()
1877 .into_checked(Default::default(), ¶ms)
1878 .unwrap();
1879 let fees = TransactionFee::checked_from_tx(
1880 &GasCosts::default(),
1881 params.fee_params(),
1882 &transaction,
1883 gas_price,
1884 )
1885 .unwrap();
1886 let real_max_fee = fees.max_fee();
1887
1888 let new_input_amount = real_max_fee;
1889 let mut new_transaction =
1890 base_asset_tx(rng, new_input_amount, gas_limit, real_max_fee);
1891 new_transaction
1892 .clone()
1893 .into_checked(Default::default(), ¶ms)
1894 .unwrap()
1895 .into_ready(gas_price, &GasCosts::default(), params.fee_params(), None)
1896 .expect("`new_transaction` should be fully valid");
1897
1898 new_transaction.witnesses_mut().push(rng.r#gen());
1901 let bigger_checked = new_transaction
1902 .into_checked(Default::default(), ¶ms)
1903 .unwrap();
1904
1905 let err = bigger_checked
1907 .into_ready(gas_price, &GasCosts::default(), params.fee_params(), None)
1908 .expect_err("Expected invalid transaction");
1909
1910 let max_fee_from_policies = match err {
1911 CheckError::InsufficientMaxFee {
1912 max_fee_from_policies,
1913 ..
1914 } => max_fee_from_policies,
1915 _ => panic!("expected insufficient max fee; found {err:?}"),
1916 };
1917
1918 assert_eq!(max_fee_from_policies, real_max_fee);
1920 }
1921
1922 #[test]
1923 fn into_checked__tx_fails_when_provided_fees_dont_cover_fee_limit() {
1924 let rng = &mut StdRng::seed_from_u64(2322u64);
1925
1926 let input_amount = 10;
1927 let factor = 1;
1928 let gas_limit = input_amount + 1; let input_amount = 10;
1933 let big_fee_limit = input_amount + 1;
1934
1935 let transaction = base_asset_tx(rng, input_amount, gas_limit, big_fee_limit);
1936
1937 let consensus_params = params(factor);
1938
1939 let err = transaction
1941 .into_checked(Default::default(), &consensus_params)
1942 .expect_err("overflow expected");
1943
1944 let provided = match err {
1946 CheckError::Validity(ValidityError::InsufficientFeeAmount {
1947 provided,
1948 ..
1949 }) => provided,
1950 _ => panic!("expected insufficient fee amount; found {err:?}"),
1951 };
1952 assert_eq!(provided, input_amount);
1953 }
1954
1955 #[test]
1956 fn into_ready__bytes_fee_cant_overflow() {
1957 let rng = &mut StdRng::seed_from_u64(2322u64);
1958
1959 let input_amount = 1000;
1960 let max_gas_price = Word::MAX;
1961 let gas_limit = 0; let zero_fee_limit = 0;
1963 let transaction = base_asset_tx(rng, input_amount, gas_limit, zero_fee_limit);
1964 let gas_costs = GasCosts::default();
1965
1966 let consensus_params = params(1);
1967
1968 let fee_params = consensus_params.fee_params();
1969 let err = transaction
1970 .into_checked(Default::default(), &consensus_params)
1971 .unwrap()
1972 .into_ready(max_gas_price, &gas_costs, fee_params, None)
1973 .expect_err("overflow expected");
1974
1975 assert_eq!(err, CheckError::Validity(ValidityError::BalanceOverflow));
1976 }
1977
1978 #[test]
1979 fn into_ready__fails_if_fee_limit_too_low() {
1980 let rng = &mut StdRng::seed_from_u64(2322u64);
1981
1982 let input_amount = 1000;
1983 let gas_price = 100;
1984 let gas_limit = 0; let gas_costs = GasCosts::default();
1986
1987 let consensus_params = params(1);
1988
1989 let fee_params = consensus_params.fee_params();
1990
1991 let zero_fee_limit = 0;
1993 let transaction = base_asset_tx(rng, input_amount, gas_limit, zero_fee_limit);
1994
1995 let err = transaction
1997 .into_checked(Default::default(), &consensus_params)
1998 .unwrap()
1999 .into_ready(gas_price, &gas_costs, fee_params, None)
2000 .expect_err("overflow expected");
2001
2002 assert!(matches!(err, CheckError::InsufficientMaxFee { .. }));
2004 }
2005
2006 #[test]
2007 fn into_ready__tx_fails_if_tip_not_covered() {
2008 let rng = &mut StdRng::seed_from_u64(2322u64);
2009
2010 let input_amount = 1;
2012 let gas_limit = 1000;
2013 let params = ConsensusParameters::standard();
2014 let block_height = 1.into();
2015 let gas_costs = GasCosts::default();
2016 let max_fee_limit = input_amount;
2017 let gas_price = 1;
2018
2019 let tx_without_tip =
2020 base_asset_tx_with_tip(rng, input_amount, gas_limit, max_fee_limit, None);
2021 tx_without_tip
2022 .clone()
2023 .into_checked(block_height, ¶ms)
2024 .unwrap()
2025 .into_ready(gas_price, &gas_costs, params.fee_params(), None)
2026 .expect("Should be valid");
2027
2028 let tip = 100;
2030 let tx_without_enough_to_pay_for_tip = base_asset_tx_with_tip(
2031 rng,
2032 input_amount,
2033 gas_limit,
2034 max_fee_limit,
2035 Some(tip),
2036 );
2037 tx_without_enough_to_pay_for_tip
2038 .into_checked(block_height, ¶ms)
2039 .unwrap()
2040 .into_ready(gas_price, &gas_costs, params.fee_params(), None)
2041 .expect_err("Expected invalid transaction");
2042
2043 let new_input_amount = input_amount + tip;
2045 let new_gas_limit = new_input_amount;
2046 let tx = base_asset_tx_with_tip(
2047 rng,
2048 new_input_amount,
2049 gas_limit,
2050 new_gas_limit,
2051 Some(tip),
2052 );
2053
2054 tx.clone()
2056 .into_checked(block_height, ¶ms)
2057 .unwrap()
2058 .into_ready(gas_price, &GasCosts::default(), params.fee_params(), None)
2059 .expect("Should be valid");
2060 }
2061
2062 #[test]
2063 fn into_ready__return_overflow_error_if_gas_price_too_high() {
2064 let rng = &mut StdRng::seed_from_u64(2322u64);
2065 let input_amount = 1000;
2066 let gas_price = Word::MAX;
2067 let gas_limit = 2; let max_fee_limit = 0;
2069
2070 let transaction = base_asset_tx(rng, input_amount, gas_limit, max_fee_limit);
2071
2072 let consensus_params = params(1);
2073
2074 let err = transaction
2075 .into_checked(Default::default(), &consensus_params)
2076 .unwrap()
2077 .into_ready(
2078 gas_price,
2079 &GasCosts::default(),
2080 consensus_params.fee_params(),
2081 None,
2082 )
2083 .expect_err("overflow expected");
2084
2085 assert_eq!(err, CheckError::Validity(ValidityError::BalanceOverflow));
2086 }
2087
2088 #[test]
2089 fn checked_tx_fails_if_asset_is_overspent_by_coin_output() {
2090 let input_amount = 1_000;
2091 let rng = &mut StdRng::seed_from_u64(2322u64);
2092 let secret = SecretKey::random(rng);
2093 let any_asset = rng.r#gen();
2094 let tx = TransactionBuilder::script(vec![], vec![])
2095 .script_gas_limit(100)
2096 .add_unsigned_coin_input(
2098 secret,
2099 rng.r#gen(),
2100 input_amount,
2101 AssetId::default(),
2102 rng.r#gen(),
2103 )
2104 .add_output(Output::change(rng.r#gen(), 0, AssetId::default()))
2105 .add_unsigned_coin_input(
2107 secret,
2108 rng.r#gen(),
2109 input_amount,
2110 any_asset,
2111 rng.r#gen(),
2112 )
2113 .add_output(Output::coin(rng.r#gen(), input_amount + 1, any_asset))
2114 .add_output(Output::change(rng.r#gen(), 0, any_asset))
2115 .finalize();
2116
2117 let checked = tx
2118 .into_checked(Default::default(), &ConsensusParameters::standard())
2119 .expect_err("Expected valid transaction");
2120
2121 assert_eq!(
2122 CheckError::Validity(ValidityError::InsufficientInputAmount {
2123 asset: any_asset,
2124 expected: input_amount + 1,
2125 provided: input_amount,
2126 }),
2127 checked
2128 );
2129 }
2130
2131 #[cfg(feature = "std")]
2132 #[test]
2133 fn basic_check_marks_basic_flag() {
2134 let block_height = 1.into();
2135
2136 let tx = Transaction::default_test_tx();
2137 let checked = tx
2139 .into_checked_basic(block_height, &ConsensusParameters::standard())
2140 .unwrap();
2141 assert!(checked.checks().contains(Checks::Basic));
2142 }
2143
2144 #[test]
2145 fn signatures_check_marks_signatures_flag() {
2146 let mut rng = StdRng::seed_from_u64(1);
2147 let block_height = 1.into();
2148 let max_fee_limit = 0;
2149
2150 let tx = valid_coin_tx(&mut rng, 100000, 1000000, 10, max_fee_limit);
2151 let chain_id = ChainId::default();
2152 let checked = tx
2153 .into_checked(
2155 block_height,
2156 &ConsensusParameters::standard_with_id(chain_id),
2157 )
2158 .unwrap()
2159 .check_signatures(&chain_id)
2161 .unwrap();
2162
2163 assert!(
2164 checked
2165 .checks()
2166 .contains(Checks::Basic | Checks::Signatures)
2167 );
2168 }
2169
2170 #[test]
2171 fn predicates_check_marks_predicate_flag() {
2172 let mut rng = StdRng::seed_from_u64(1);
2173 let block_height = 1.into();
2174 let gas_costs = GasCosts::default();
2175
2176 let tx = predicate_tx(&mut rng, 1000000, 1000000, 1000000, gas_costs.ret());
2177
2178 let mut consensus_params = ConsensusParameters::standard();
2179 consensus_params.set_gas_costs(gas_costs);
2180
2181 let check_predicate_params = CheckPredicateParams::from(&consensus_params);
2182
2183 let checked = tx
2184 .into_checked(
2186 block_height,
2187 &consensus_params,
2188 )
2189 .unwrap()
2190 .check_predicates(&check_predicate_params, MemoryInstance::new(), &EmptyStorage, NotSupportedEcal)
2192 .unwrap();
2193 assert!(
2194 checked
2195 .checks()
2196 .contains(Checks::Basic | Checks::Predicates)
2197 );
2198 }
2199
2200 fn is_valid_max_fee(
2201 tx: &Script,
2202 gas_price: u64,
2203 gas_costs: &GasCosts,
2204 fee_params: &FeeParameters,
2205 ) -> Result<bool, ValidityError> {
2206 fn gas_to_fee(gas: u64, price: u64, factor: u64) -> u128 {
2207 let prices_gas = gas as u128 * price as u128;
2208 let fee = prices_gas / factor as u128;
2209 let fee_remainder = (prices_gas.rem_euclid(factor as u128) > 0) as u128;
2210 fee + fee_remainder
2211 }
2212
2213 let gas_used_by_bytes = fee_params
2215 .gas_per_byte()
2216 .saturating_mul(tx.metered_bytes_size() as u64);
2217 let gas_used_by_inputs = tx.gas_used_by_inputs(gas_costs);
2218 let gas_used_by_metadata = tx.gas_used_by_metadata(gas_costs);
2219 let min_gas = gas_used_by_bytes
2220 .saturating_add(gas_used_by_inputs)
2221 .saturating_add(gas_used_by_metadata)
2222 .saturating_add(
2223 gas_costs
2224 .vm_initialization()
2225 .resolve(tx.metered_bytes_size() as u64),
2226 );
2227
2228 let witness_limit_allowance = tx
2230 .witness_limit()
2231 .saturating_sub(tx.witnesses().size_dynamic() as u64)
2232 .saturating_mul(fee_params.gas_per_byte());
2233 let max_gas = min_gas
2234 .saturating_add(*tx.script_gas_limit())
2235 .saturating_add(witness_limit_allowance);
2236 let max_fee = gas_to_fee(max_gas, gas_price, fee_params.gas_price_factor());
2237
2238 let max_fee_with_tip = max_fee.saturating_add(tx.tip() as u128);
2239
2240 let result = max_fee_with_tip == tx.max_fee(gas_costs, fee_params, gas_price);
2241 Ok(result)
2242 }
2243
2244 fn is_valid_min_fee<Tx>(
2245 tx: &Tx,
2246 gas_costs: &GasCosts,
2247 fee_params: &FeeParameters,
2248 gas_price: u64,
2249 ) -> Result<bool, ValidityError>
2250 where
2251 Tx: Chargeable + field::Inputs + field::Outputs,
2252 {
2253 let gas_used_by_bytes = fee_params
2256 .gas_per_byte()
2257 .saturating_mul(tx.metered_bytes_size() as u64);
2258 let gas_used_by_inputs = tx.gas_used_by_inputs(gas_costs);
2259 let gas_used_by_metadata = tx.gas_used_by_metadata(gas_costs);
2260 let gas = gas_used_by_bytes
2261 .saturating_add(gas_used_by_inputs)
2262 .saturating_add(gas_used_by_metadata)
2263 .saturating_add(
2264 gas_costs
2265 .vm_initialization()
2266 .resolve(tx.metered_bytes_size() as u64),
2267 );
2268 let total = gas as u128 * gas_price as u128;
2269 let fee = total / fee_params.gas_price_factor() as u128;
2271 let fee_remainder =
2272 (total.rem_euclid(fee_params.gas_price_factor() as u128) > 0) as u128;
2273 let rounded_fee = fee
2274 .saturating_add(fee_remainder)
2275 .saturating_add(tx.tip() as u128);
2276 let min_fee = rounded_fee;
2277 let calculated_min_fee = tx.min_fee(gas_costs, fee_params, gas_price);
2278
2279 Ok(min_fee == calculated_min_fee)
2280 }
2281
2282 fn valid_coin_tx(
2283 rng: &mut StdRng,
2284 gas_limit: u64,
2285 input_amount: u64,
2286 output_amount: u64,
2287 max_fee_limit: u64,
2288 ) -> Script {
2289 let asset = AssetId::default();
2290 TransactionBuilder::script(vec![], vec![])
2291 .script_gas_limit(gas_limit)
2292 .max_fee_limit(max_fee_limit)
2293 .add_unsigned_coin_input(
2294 SecretKey::random(rng),
2295 rng.r#gen(),
2296 input_amount,
2297 asset,
2298 rng.r#gen(),
2299 )
2300 .add_input(Input::contract(
2301 rng.r#gen(),
2302 rng.r#gen(),
2303 rng.r#gen(),
2304 rng.r#gen(),
2305 rng.r#gen(),
2306 ))
2307 .add_output(Output::contract(1, rng.r#gen(), rng.r#gen()))
2308 .add_output(Output::coin(rng.r#gen(), output_amount, asset))
2309 .add_output(Output::change(rng.r#gen(), 0, asset))
2310 .finalize()
2311 }
2312
2313 fn predicate_tx(
2315 rng: &mut StdRng,
2316 gas_limit: u64,
2317 witness_limit: u64,
2318 fee_input_amount: u64,
2319 predicate_gas_used: u64,
2320 ) -> Script {
2321 let asset = AssetId::default();
2322 let predicate = vec![op::ret(1)].into_iter().collect::<Vec<u8>>();
2323 let owner = Input::predicate_owner(&predicate);
2324 let zero_fee_limit = 0;
2325 TransactionBuilder::script(vec![], vec![])
2326 .max_fee_limit(zero_fee_limit)
2327 .script_gas_limit(gas_limit)
2328 .witness_limit(witness_limit)
2329 .add_input(Input::coin_predicate(
2330 rng.r#gen(),
2331 owner,
2332 fee_input_amount,
2333 asset,
2334 rng.r#gen(),
2335 predicate_gas_used,
2336 predicate,
2337 vec![],
2338 ))
2339 .add_output(Output::change(rng.r#gen(), 0, asset))
2340 .finalize()
2341 }
2342
2343 fn signed_message_coin_tx(
2345 rng: &mut StdRng,
2346 gas_limit: u64,
2347 input_amount: u64,
2348 max_fee: u64,
2349 ) -> Script {
2350 TransactionBuilder::script(vec![], vec![])
2351 .max_fee_limit(max_fee)
2352 .script_gas_limit(gas_limit)
2353 .add_unsigned_message_input(
2354 SecretKey::random(rng),
2355 rng.r#gen(),
2356 rng.r#gen(),
2357 input_amount,
2358 vec![],
2359 )
2360 .finalize()
2361 }
2362
2363 fn predicate_message_coin_tx(
2364 rng: &mut StdRng,
2365 gas_limit: u64,
2366 input_amount: u64,
2367 tip: u64,
2368 ) -> Script {
2369 TransactionBuilder::script(vec![], vec![])
2370 .tip(tip)
2371 .script_gas_limit(gas_limit)
2372 .add_input(Input::message_coin_predicate(
2373 rng.r#gen(),
2374 rng.r#gen(),
2375 input_amount,
2376 rng.r#gen(),
2377 Default::default(),
2378 vec![],
2379 vec![],
2380 ))
2381 .finalize()
2382 }
2383
2384 fn base_asset_tx(
2385 rng: &mut StdRng,
2386 input_amount: u64,
2387 gas_limit: u64,
2388 max_fee: u64,
2389 ) -> Script {
2390 base_asset_tx_with_tip(rng, input_amount, gas_limit, max_fee, None)
2391 }
2392
2393 fn base_asset_tx_with_tip(
2394 rng: &mut StdRng,
2395 input_amount: u64,
2396 gas_limit: u64,
2397 max_fee: u64,
2398 tip: Option<u64>,
2399 ) -> Script {
2400 let mut builder = TransactionBuilder::script(vec![], vec![]);
2401 if let Some(tip) = tip {
2402 builder.tip(tip);
2403 }
2404 builder
2405 .max_fee_limit(max_fee)
2406 .script_gas_limit(gas_limit)
2407 .add_unsigned_coin_input(
2408 SecretKey::random(rng),
2409 rng.r#gen(),
2410 input_amount,
2411 AssetId::default(),
2412 rng.r#gen(),
2413 )
2414 .add_output(Output::change(rng.r#gen(), 0, AssetId::default()))
2415 .finalize()
2416 }
2417}