fuel_vm/
checked_transaction.rs

1//! A checked transaction is type-wrapper for transactions which have been checked.
2//! It is impossible to construct a checked transaction without performing necessary
3//! checks.
4//!
5//! This allows the VM to accept transactions with metadata that have been already
6//! verified upstream.
7
8#![allow(non_upper_case_globals)]
9
10use fuel_tx::{
11    field::Expiration,
12    Create,
13    Mint,
14    Script,
15    Transaction,
16    ValidityError,
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    field::MaxFeeLimit,
34    ConsensusParameters,
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        Memory,
48        MemoryInstance,
49    },
50    pool::VmMemoryPool,
51    prelude::*,
52    storage::predicate::{
53        EmptyStorage,
54        PredicateStorageProvider,
55        PredicateStorageRequirements,
56    },
57};
58
59bitflags::bitflags! {
60    /// Possible types of transaction checks.
61    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
62    pub struct Checks: u32 {
63        /// Basic checks defined in the specification for each transaction:
64        /// https://github.com/FuelLabs/fuel-specs/blob/master/src/tx-format/transaction.md#transaction
65        /// Also ensures that malleable fields are zeroed.
66        const Basic         = 0b00000001;
67        /// Check that signature in the transactions are valid.
68        const Signatures    = 0b00000010;
69        /// Check that predicate in the transactions are valid.
70        const Predicates    = 0b00000100;
71    }
72}
73
74impl core::fmt::Display for Checks {
75    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
76        write!(f, "{:032b}", self.bits())
77    }
78}
79
80/// The type describes that the inner transaction was already checked.
81///
82/// All fields are private, and there is no constructor, so it is impossible to create the
83/// instance of `Checked` outside the `fuel-tx` crate.
84///
85/// The inner data is immutable to prevent modification to invalidate the checking.
86///
87/// If you need to modify an inner state, you need to get inner values
88/// (via the `Into<(Tx, Tx ::Metadata)>` trait), modify them and check again.
89///
90/// # Dev note: Avoid serde serialization of this type.
91///
92/// Since checked tx would need to be re-validated on deserialization anyways,
93/// it's cleaner to redo the tx check.
94#[derive(Debug, Clone, Eq, PartialEq, Hash)]
95pub struct Checked<Tx: IntoChecked> {
96    transaction: Tx,
97    metadata: Tx::Metadata,
98    checks_bitmask: Checks,
99}
100
101impl<Tx: IntoChecked> Checked<Tx> {
102    fn new(transaction: Tx, metadata: Tx::Metadata, checks_bitmask: Checks) -> Self {
103        Checked {
104            transaction,
105            metadata,
106            checks_bitmask,
107        }
108    }
109
110    pub(crate) fn basic(transaction: Tx, metadata: Tx::Metadata) -> Self {
111        Checked::new(transaction, metadata, Checks::Basic)
112    }
113
114    /// Returns reference on inner transaction.
115    pub fn transaction(&self) -> &Tx {
116        &self.transaction
117    }
118
119    /// Returns the metadata generated during the check for transaction.
120    pub fn metadata(&self) -> &Tx::Metadata {
121        &self.metadata
122    }
123
124    /// Returns the bitmask of all passed checks.
125    pub fn checks(&self) -> &Checks {
126        &self.checks_bitmask
127    }
128
129    /// Performs check of signatures, if not yet done.
130    pub fn check_signatures(mut self, chain_id: &ChainId) -> Result<Self, CheckError> {
131        if !self.checks_bitmask.contains(Checks::Signatures) {
132            self.transaction.check_signatures(chain_id)?;
133            self.checks_bitmask.insert(Checks::Signatures);
134        }
135        Ok(self)
136    }
137}
138
139/// Transaction that has checks for all dynamic values, e.g. `gas_price`
140#[derive(Debug, Clone, Eq, PartialEq, Hash)]
141pub struct Ready<Tx: IntoChecked> {
142    gas_price: Word,
143    transaction: Tx,
144    metadata: Tx::Metadata,
145    checks_bitmask: Checks,
146}
147
148impl<Tx: IntoChecked> Ready<Tx> {
149    /// Consume and decompose components of the `Immutable` transaction.
150    pub fn decompose(self) -> (Word, Checked<Tx>) {
151        let Ready {
152            gas_price,
153            transaction,
154            metadata,
155            checks_bitmask,
156        } = self;
157        let checked = Checked::new(transaction, metadata, checks_bitmask);
158        (gas_price, checked)
159    }
160
161    /// Getter for `gas_price` field
162    pub fn gas_price(&self) -> Word {
163        self.gas_price
164    }
165}
166
167#[cfg(feature = "test-helpers")]
168impl<Tx: IntoChecked> Checked<Tx> {
169    /// Convert `Checked` into `Ready` without performing final checks.
170    pub fn test_into_ready(self) -> Ready<Tx> {
171        let Checked {
172            transaction,
173            metadata,
174            checks_bitmask,
175        } = self;
176        Ready {
177            gas_price: 0,
178            transaction,
179            metadata,
180            checks_bitmask,
181        }
182    }
183}
184
185impl<Tx: IntoChecked + Chargeable> Checked<Tx> {
186    /// Run final checks on `Checked` using dynamic values, e.g. `gas_price`
187    pub fn into_ready(
188        self,
189        gas_price: Word,
190        gas_costs: &GasCosts,
191        fee_parameters: &FeeParameters,
192        block_height: Option<BlockHeight>,
193    ) -> Result<Ready<Tx>, CheckError> {
194        let Checked {
195            transaction,
196            metadata,
197            checks_bitmask,
198        } = self;
199        let fee = TransactionFee::checked_from_tx(
200            gas_costs,
201            fee_parameters,
202            &transaction,
203            gas_price,
204        )
205        .ok_or(CheckError::Validity(ValidityError::BalanceOverflow))?;
206
207        let max_fee_from_policies = transaction.max_fee_limit();
208        let max_fee_from_gas_price = fee.max_fee();
209
210        if let Some(block_height) = block_height {
211            if block_height > transaction.expiration() {
212                return Err(CheckError::Validity(ValidityError::TransactionExpiration));
213            }
214        }
215
216        if max_fee_from_gas_price > max_fee_from_policies {
217            Err(CheckError::InsufficientMaxFee {
218                max_fee_from_policies,
219                max_fee_from_gas_price,
220            })
221        } else {
222            Ok(Ready {
223                gas_price,
224                transaction,
225                metadata,
226                checks_bitmask,
227            })
228        }
229    }
230}
231
232impl<Tx: IntoChecked + UniqueIdentifier> Checked<Tx> {
233    /// Returns the transaction ID from the computed metadata
234    pub fn id(&self) -> TxId {
235        self.transaction
236            .cached_id()
237            .expect("Transaction metadata should be computed for checked transactions")
238    }
239}
240
241#[cfg(feature = "test-helpers")]
242impl<Tx: IntoChecked + Default> Default for Checked<Tx>
243where
244    Checked<Tx>: CheckPredicates,
245{
246    fn default() -> Self {
247        Tx::default()
248            .into_checked(Default::default(), &ConsensusParameters::standard())
249            .expect("default tx should produce a valid fully checked transaction")
250    }
251}
252
253impl<Tx: IntoChecked> From<Checked<Tx>> for (Tx, Tx::Metadata) {
254    fn from(checked: Checked<Tx>) -> Self {
255        let Checked {
256            transaction,
257            metadata,
258            ..
259        } = checked;
260
261        (transaction, metadata)
262    }
263}
264
265impl<Tx: IntoChecked> AsRef<Tx> for Checked<Tx> {
266    fn as_ref(&self) -> &Tx {
267        &self.transaction
268    }
269}
270
271#[cfg(feature = "test-helpers")]
272impl<Tx: IntoChecked> AsMut<Tx> for Checked<Tx> {
273    fn as_mut(&mut self) -> &mut Tx {
274        &mut self.transaction
275    }
276}
277
278impl<Tx: IntoChecked> Borrow<Tx> for Checked<Tx> {
279    fn borrow(&self) -> &Tx {
280        self.transaction()
281    }
282}
283
284/// The error can occur when transforming transactions into the `Checked` type.
285#[derive(Debug, Clone, PartialEq)]
286#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
287pub enum CheckError {
288    /// The transaction doesn't pass validity rules.
289    Validity(ValidityError),
290    /// The predicate verification failed.
291    PredicateVerificationFailed(PredicateVerificationFailed),
292    /// The max fee used during checking was lower than calculated during `Immutable`
293    /// conversion
294    InsufficientMaxFee {
295        /// The max fee from the policies defined by the user.
296        max_fee_from_policies: Word,
297        /// The max fee calculated from the gas price and gas used by the transaction.
298        max_fee_from_gas_price: Word,
299    },
300}
301
302/// Performs checks for a transaction
303pub trait IntoChecked: FormatValidityChecks + Sized {
304    /// Metadata produced during the check.
305    type Metadata: Sized;
306
307    /// Returns transaction that passed all `Checks`.
308    fn into_checked(
309        self,
310        block_height: BlockHeight,
311        consensus_params: &ConsensusParameters,
312    ) -> Result<Checked<Self>, CheckError>
313    where
314        Checked<Self>: CheckPredicates,
315    {
316        self.into_checked_reusable_memory(
317            block_height,
318            consensus_params,
319            MemoryInstance::new(),
320            &EmptyStorage,
321        )
322    }
323
324    /// Returns transaction that passed all `Checks` accepting reusable memory
325    /// to run predicates.
326    fn into_checked_reusable_memory(
327        self,
328        block_height: BlockHeight,
329        consensus_params: &ConsensusParameters,
330        memory: impl Memory,
331        storage: &impl PredicateStorageRequirements,
332    ) -> Result<Checked<Self>, CheckError>
333    where
334        Checked<Self>: CheckPredicates,
335    {
336        let check_predicate_params = consensus_params.into();
337        self.into_checked_basic(block_height, consensus_params)?
338            .check_signatures(&consensus_params.chain_id())?
339            .check_predicates(&check_predicate_params, memory, storage)
340    }
341
342    /// Returns transaction that passed only `Checks::Basic`.
343    fn into_checked_basic(
344        self,
345        block_height: BlockHeight,
346        consensus_params: &ConsensusParameters,
347    ) -> Result<Checked<Self>, CheckError>;
348}
349
350/// The parameters needed for checking a predicate
351#[derive(Debug, Clone)]
352pub struct CheckPredicateParams {
353    /// Gas costs for opcodes
354    pub gas_costs: GasCosts,
355    /// Chain ID
356    pub chain_id: ChainId,
357    /// Maximum gas per predicate
358    pub max_gas_per_predicate: u64,
359    /// Maximum gas per transaction
360    pub max_gas_per_tx: u64,
361    /// Maximum number of inputs
362    pub max_inputs: u16,
363    /// Maximum size of the contract in bytes
364    pub contract_max_size: u64,
365    /// Maximum length of the message data
366    pub max_message_data_length: u64,
367    /// Offset of the transaction data in the memory
368    pub tx_offset: usize,
369    /// Fee parameters
370    pub fee_params: FeeParameters,
371    /// Base Asset ID
372    pub base_asset_id: AssetId,
373}
374
375#[cfg(feature = "test-helpers")]
376impl Default for CheckPredicateParams {
377    fn default() -> Self {
378        CheckPredicateParams::from(&ConsensusParameters::standard())
379    }
380}
381
382impl From<ConsensusParameters> for CheckPredicateParams {
383    fn from(value: ConsensusParameters) -> Self {
384        CheckPredicateParams::from(&value)
385    }
386}
387
388impl From<&ConsensusParameters> for CheckPredicateParams {
389    fn from(value: &ConsensusParameters) -> Self {
390        CheckPredicateParams {
391            gas_costs: value.gas_costs().clone(),
392            chain_id: value.chain_id(),
393            max_gas_per_predicate: value.predicate_params().max_gas_per_predicate(),
394            max_gas_per_tx: value.tx_params().max_gas_per_tx(),
395            max_inputs: value.tx_params().max_inputs(),
396            contract_max_size: value.contract_params().contract_max_size(),
397            max_message_data_length: value.predicate_params().max_message_data_length(),
398            tx_offset: value.tx_params().tx_offset(),
399            fee_params: *(value.fee_params()),
400            base_asset_id: *value.base_asset_id(),
401        }
402    }
403}
404
405/// Provides predicate verification functionality for the transaction.
406#[async_trait::async_trait]
407pub trait CheckPredicates: Sized {
408    /// Performs predicates verification of the transaction.
409    fn check_predicates(
410        self,
411        params: &CheckPredicateParams,
412        memory: impl Memory,
413        storage: &impl PredicateStorageRequirements,
414    ) -> Result<Self, CheckError>;
415
416    /// Performs predicates verification of the transaction in parallel.
417    async fn check_predicates_async<E: ParallelExecutor>(
418        self,
419        params: &CheckPredicateParams,
420        pool: &impl VmMemoryPool,
421        storage: &impl PredicateStorageProvider,
422    ) -> Result<Self, CheckError>;
423}
424
425/// Provides predicate estimation functionality for the transaction.
426#[async_trait::async_trait]
427pub trait EstimatePredicates: Sized {
428    /// Estimates predicates of the transaction.
429    fn estimate_predicates(
430        &mut self,
431        params: &CheckPredicateParams,
432        memory: impl Memory,
433        storage: &impl PredicateStorageRequirements,
434    ) -> Result<(), CheckError>;
435
436    /// Estimates predicates of the transaction in parallel.
437    async fn estimate_predicates_async<E: ParallelExecutor>(
438        &mut self,
439        params: &CheckPredicateParams,
440        pool: &impl VmMemoryPool,
441        storage: &impl PredicateStorageProvider,
442    ) -> Result<(), CheckError>;
443}
444
445/// Executes CPU-heavy tasks in parallel.
446#[async_trait::async_trait]
447pub trait ParallelExecutor {
448    /// Future created from a CPU-heavy task.
449    type Task: Future + Send + 'static;
450
451    /// Creates a Future from a CPU-heavy task.
452    fn create_task<F>(func: F) -> Self::Task
453    where
454        F: FnOnce() -> Result<(Word, usize), PredicateVerificationFailed>
455            + Send
456            + 'static;
457
458    /// Executes tasks created by `create_task` in parallel.
459    async fn execute_tasks(
460        futures: Vec<Self::Task>,
461    ) -> Vec<Result<(Word, usize), PredicateVerificationFailed>>;
462}
463
464#[async_trait::async_trait]
465impl<Tx> CheckPredicates for Checked<Tx>
466where
467    Tx: ExecutableTransaction + Send + Sync + 'static,
468    <Tx as IntoChecked>::Metadata: crate::interpreter::CheckedMetadata + Send + Sync,
469{
470    fn check_predicates(
471        mut self,
472        params: &CheckPredicateParams,
473        memory: impl Memory,
474        storage: &impl PredicateStorageRequirements,
475    ) -> Result<Self, CheckError> {
476        if !self.checks_bitmask.contains(Checks::Predicates) {
477            predicates::check_predicates(&self, params, memory, storage)?;
478            self.checks_bitmask.insert(Checks::Predicates);
479        }
480        Ok(self)
481    }
482
483    async fn check_predicates_async<E>(
484        mut self,
485        params: &CheckPredicateParams,
486        pool: &impl VmMemoryPool,
487        storage: &impl PredicateStorageProvider,
488    ) -> Result<Self, CheckError>
489    where
490        E: ParallelExecutor,
491    {
492        if !self.checks_bitmask.contains(Checks::Predicates) {
493            predicates::check_predicates_async::<Tx, E>(&self, params, pool, storage)
494                .await?;
495
496            self.checks_bitmask.insert(Checks::Predicates);
497
498            Ok(self)
499        } else {
500            Ok(self)
501        }
502    }
503}
504
505#[async_trait::async_trait]
506impl<Tx: ExecutableTransaction + Send + Sync + 'static> EstimatePredicates for Tx {
507    fn estimate_predicates(
508        &mut self,
509        params: &CheckPredicateParams,
510        memory: impl Memory,
511        storage: &impl PredicateStorageRequirements,
512    ) -> Result<(), CheckError> {
513        predicates::estimate_predicates(self, params, memory, storage)?;
514        Ok(())
515    }
516
517    async fn estimate_predicates_async<E>(
518        &mut self,
519        params: &CheckPredicateParams,
520        pool: &impl VmMemoryPool,
521        storage: &impl PredicateStorageProvider,
522    ) -> Result<(), CheckError>
523    where
524        E: ParallelExecutor,
525    {
526        predicates::estimate_predicates_async::<Self, E>(self, params, pool, storage)
527            .await?;
528
529        Ok(())
530    }
531}
532
533#[async_trait::async_trait]
534impl EstimatePredicates for Transaction {
535    fn estimate_predicates(
536        &mut self,
537        params: &CheckPredicateParams,
538        memory: impl Memory,
539        storage: &impl PredicateStorageRequirements,
540    ) -> Result<(), CheckError> {
541        match self {
542            Self::Script(tx) => tx.estimate_predicates(params, memory, storage),
543            Self::Create(tx) => tx.estimate_predicates(params, memory, storage),
544            Self::Mint(_) => Ok(()),
545            Self::Upgrade(tx) => tx.estimate_predicates(params, memory, storage),
546            Self::Upload(tx) => tx.estimate_predicates(params, memory, storage),
547            Self::Blob(tx) => tx.estimate_predicates(params, memory, storage),
548        }
549    }
550
551    async fn estimate_predicates_async<E: ParallelExecutor>(
552        &mut self,
553        params: &CheckPredicateParams,
554        pool: &impl VmMemoryPool,
555        storage: &impl PredicateStorageProvider,
556    ) -> Result<(), CheckError> {
557        match self {
558            Self::Script(tx) => {
559                tx.estimate_predicates_async::<E>(params, pool, storage)
560                    .await
561            }
562            Self::Create(tx) => {
563                tx.estimate_predicates_async::<E>(params, pool, storage)
564                    .await
565            }
566            Self::Mint(_) => Ok(()),
567            Self::Upgrade(tx) => {
568                tx.estimate_predicates_async::<E>(params, pool, storage)
569                    .await
570            }
571            Self::Upload(tx) => {
572                tx.estimate_predicates_async::<E>(params, pool, storage)
573                    .await
574            }
575            Self::Blob(tx) => {
576                tx.estimate_predicates_async::<E>(params, pool, storage)
577                    .await
578            }
579        }
580    }
581}
582
583#[async_trait::async_trait]
584impl CheckPredicates for Checked<Mint> {
585    fn check_predicates(
586        mut self,
587        _params: &CheckPredicateParams,
588        _memory: impl Memory,
589        _storage: &impl PredicateStorageRequirements,
590    ) -> Result<Self, CheckError> {
591        self.checks_bitmask.insert(Checks::Predicates);
592        Ok(self)
593    }
594
595    async fn check_predicates_async<E: ParallelExecutor>(
596        mut self,
597        _params: &CheckPredicateParams,
598        _pool: &impl VmMemoryPool,
599        _storage: &impl PredicateStorageProvider,
600    ) -> Result<Self, CheckError> {
601        self.checks_bitmask.insert(Checks::Predicates);
602        Ok(self)
603    }
604}
605
606#[async_trait::async_trait]
607impl CheckPredicates for Checked<Transaction> {
608    fn check_predicates(
609        self,
610        params: &CheckPredicateParams,
611        memory: impl Memory,
612        storage: &impl PredicateStorageRequirements,
613    ) -> Result<Self, CheckError> {
614        let checked_transaction: CheckedTransaction = self.into();
615        let checked_transaction: CheckedTransaction = match checked_transaction {
616            CheckedTransaction::Script(tx) => {
617                CheckPredicates::check_predicates(tx, params, memory, storage)?.into()
618            }
619            CheckedTransaction::Create(tx) => {
620                CheckPredicates::check_predicates(tx, params, memory, storage)?.into()
621            }
622            CheckedTransaction::Mint(tx) => {
623                CheckPredicates::check_predicates(tx, params, memory, storage)?.into()
624            }
625            CheckedTransaction::Upgrade(tx) => {
626                CheckPredicates::check_predicates(tx, params, memory, storage)?.into()
627            }
628            CheckedTransaction::Upload(tx) => {
629                CheckPredicates::check_predicates(tx, params, memory, storage)?.into()
630            }
631            CheckedTransaction::Blob(tx) => {
632                CheckPredicates::check_predicates(tx, params, memory, storage)?.into()
633            }
634        };
635        Ok(checked_transaction.into())
636    }
637
638    async fn check_predicates_async<E>(
639        mut self,
640        params: &CheckPredicateParams,
641        pool: &impl VmMemoryPool,
642        storage: &impl PredicateStorageProvider,
643    ) -> Result<Self, CheckError>
644    where
645        E: ParallelExecutor,
646    {
647        let checked_transaction: CheckedTransaction = self.into();
648
649        let checked_transaction: CheckedTransaction = match checked_transaction {
650            CheckedTransaction::Script(tx) => {
651                CheckPredicates::check_predicates_async::<E>(tx, params, pool, storage)
652                    .await?
653                    .into()
654            }
655            CheckedTransaction::Create(tx) => {
656                CheckPredicates::check_predicates_async::<E>(tx, params, pool, storage)
657                    .await?
658                    .into()
659            }
660            CheckedTransaction::Mint(tx) => {
661                CheckPredicates::check_predicates_async::<E>(tx, params, pool, storage)
662                    .await?
663                    .into()
664            }
665            CheckedTransaction::Upgrade(tx) => {
666                CheckPredicates::check_predicates_async::<E>(tx, params, pool, storage)
667                    .await?
668                    .into()
669            }
670            CheckedTransaction::Upload(tx) => {
671                CheckPredicates::check_predicates_async::<E>(tx, params, pool, storage)
672                    .await?
673                    .into()
674            }
675            CheckedTransaction::Blob(tx) => {
676                CheckPredicates::check_predicates_async::<E>(tx, params, pool, storage)
677                    .await?
678                    .into()
679            }
680        };
681
682        Ok(checked_transaction.into())
683    }
684}
685
686/// The Enum version of `Checked<Transaction>` allows getting the inner variant without
687/// losing "checked" status.
688///
689/// It is possible to freely convert `Checked<Transaction>` into `CheckedTransaction` and
690/// vice verse without the overhead.
691#[derive(Debug, Clone, Eq, PartialEq, Hash)]
692#[allow(missing_docs)]
693pub enum CheckedTransaction {
694    Script(Checked<Script>),
695    Create(Checked<Create>),
696    Mint(Checked<Mint>),
697    Upgrade(Checked<Upgrade>),
698    Upload(Checked<Upload>),
699    Blob(Checked<Blob>),
700}
701
702impl From<Checked<Transaction>> for CheckedTransaction {
703    fn from(checked: Checked<Transaction>) -> Self {
704        let Checked {
705            transaction,
706            metadata,
707            checks_bitmask,
708        } = checked;
709
710        // # Dev note: Avoid wildcard pattern to be sure that all variants are covered.
711        match (transaction, metadata) {
712            (Transaction::Script(transaction), CheckedMetadata::Script(metadata)) => {
713                Self::Script(Checked::new(transaction, metadata, checks_bitmask))
714            }
715            (Transaction::Create(transaction), CheckedMetadata::Create(metadata)) => {
716                Self::Create(Checked::new(transaction, metadata, checks_bitmask))
717            }
718            (Transaction::Mint(transaction), CheckedMetadata::Mint(metadata)) => {
719                Self::Mint(Checked::new(transaction, metadata, checks_bitmask))
720            }
721            (Transaction::Upgrade(transaction), CheckedMetadata::Upgrade(metadata)) => {
722                Self::Upgrade(Checked::new(transaction, metadata, checks_bitmask))
723            }
724            (Transaction::Upload(transaction), CheckedMetadata::Upload(metadata)) => {
725                Self::Upload(Checked::new(transaction, metadata, checks_bitmask))
726            }
727            (Transaction::Blob(transaction), CheckedMetadata::Blob(metadata)) => {
728                Self::Blob(Checked::new(transaction, metadata, checks_bitmask))
729            }
730            // The code should produce the `CheckedMetadata` for the corresponding
731            // transaction variant. It is done in the implementation of the
732            // `IntoChecked` trait for `Transaction`. With the current
733            // implementation, the patterns below are unreachable.
734            (Transaction::Script(_), _) => unreachable!(),
735            (Transaction::Create(_), _) => unreachable!(),
736            (Transaction::Mint(_), _) => unreachable!(),
737            (Transaction::Upgrade(_), _) => unreachable!(),
738            (Transaction::Upload(_), _) => unreachable!(),
739            (Transaction::Blob(_), _) => unreachable!(),
740        }
741    }
742}
743
744impl From<Checked<Script>> for CheckedTransaction {
745    fn from(checked: Checked<Script>) -> Self {
746        Self::Script(checked)
747    }
748}
749
750impl From<Checked<Create>> for CheckedTransaction {
751    fn from(checked: Checked<Create>) -> Self {
752        Self::Create(checked)
753    }
754}
755
756impl From<Checked<Mint>> for CheckedTransaction {
757    fn from(checked: Checked<Mint>) -> Self {
758        Self::Mint(checked)
759    }
760}
761
762impl From<Checked<Upgrade>> for CheckedTransaction {
763    fn from(checked: Checked<Upgrade>) -> Self {
764        Self::Upgrade(checked)
765    }
766}
767
768impl From<Checked<Upload>> for CheckedTransaction {
769    fn from(checked: Checked<Upload>) -> Self {
770        Self::Upload(checked)
771    }
772}
773
774impl From<Checked<Blob>> for CheckedTransaction {
775    fn from(checked: Checked<Blob>) -> Self {
776        Self::Blob(checked)
777    }
778}
779
780impl From<CheckedTransaction> for Checked<Transaction> {
781    fn from(checked: CheckedTransaction) -> Self {
782        match checked {
783            CheckedTransaction::Script(Checked {
784                transaction,
785                metadata,
786                checks_bitmask,
787            }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
788            CheckedTransaction::Create(Checked {
789                transaction,
790                metadata,
791                checks_bitmask,
792            }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
793            CheckedTransaction::Mint(Checked {
794                transaction,
795                metadata,
796                checks_bitmask,
797            }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
798            CheckedTransaction::Upgrade(Checked {
799                transaction,
800                metadata,
801                checks_bitmask,
802            }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
803            CheckedTransaction::Upload(Checked {
804                transaction,
805                metadata,
806                checks_bitmask,
807            }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
808            CheckedTransaction::Blob(Checked {
809                transaction,
810                metadata,
811                checks_bitmask,
812            }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
813        }
814    }
815}
816
817/// The `IntoChecked` metadata for `CheckedTransaction`.
818#[derive(Debug, Clone, Eq, PartialEq, Hash)]
819#[allow(missing_docs)]
820pub enum CheckedMetadata {
821    Script(<Script as IntoChecked>::Metadata),
822    Create(<Create as IntoChecked>::Metadata),
823    Mint(<Mint as IntoChecked>::Metadata),
824    Upgrade(<Upgrade as IntoChecked>::Metadata),
825    Upload(<Upload as IntoChecked>::Metadata),
826    Blob(<Blob as IntoChecked>::Metadata),
827}
828
829impl From<<Script as IntoChecked>::Metadata> for CheckedMetadata {
830    fn from(metadata: <Script as IntoChecked>::Metadata) -> Self {
831        Self::Script(metadata)
832    }
833}
834
835impl From<<Create as IntoChecked>::Metadata> for CheckedMetadata {
836    fn from(metadata: <Create as IntoChecked>::Metadata) -> Self {
837        Self::Create(metadata)
838    }
839}
840
841impl From<<Mint as IntoChecked>::Metadata> for CheckedMetadata {
842    fn from(metadata: <Mint as IntoChecked>::Metadata) -> Self {
843        Self::Mint(metadata)
844    }
845}
846
847impl From<<Upgrade as IntoChecked>::Metadata> for CheckedMetadata {
848    fn from(metadata: <Upgrade as IntoChecked>::Metadata) -> Self {
849        Self::Upgrade(metadata)
850    }
851}
852
853impl From<<Upload as IntoChecked>::Metadata> for CheckedMetadata {
854    fn from(metadata: <Upload as IntoChecked>::Metadata) -> Self {
855        Self::Upload(metadata)
856    }
857}
858impl From<<Blob as IntoChecked>::Metadata> for CheckedMetadata {
859    fn from(metadata: <Blob as IntoChecked>::Metadata) -> Self {
860        Self::Blob(metadata)
861    }
862}
863
864impl IntoChecked for Transaction {
865    type Metadata = CheckedMetadata;
866
867    fn into_checked_basic(
868        self,
869        block_height: BlockHeight,
870        consensus_params: &ConsensusParameters,
871    ) -> Result<Checked<Self>, CheckError> {
872        match self {
873            Self::Script(tx) => {
874                let (transaction, metadata) = tx
875                    .into_checked_basic(block_height, consensus_params)?
876                    .into();
877                Ok((transaction.into(), metadata.into()))
878            }
879            Self::Create(tx) => {
880                let (transaction, metadata) = tx
881                    .into_checked_basic(block_height, consensus_params)?
882                    .into();
883                Ok((transaction.into(), metadata.into()))
884            }
885            Self::Mint(tx) => {
886                let (transaction, metadata) = tx
887                    .into_checked_basic(block_height, consensus_params)?
888                    .into();
889                Ok((transaction.into(), metadata.into()))
890            }
891            Self::Upgrade(tx) => {
892                let (transaction, metadata) = tx
893                    .into_checked_basic(block_height, consensus_params)?
894                    .into();
895                Ok((transaction.into(), metadata.into()))
896            }
897            Self::Upload(tx) => {
898                let (transaction, metadata) = tx
899                    .into_checked_basic(block_height, consensus_params)?
900                    .into();
901                Ok((transaction.into(), metadata.into()))
902            }
903            Self::Blob(tx) => {
904                let (transaction, metadata) = tx
905                    .into_checked_basic(block_height, consensus_params)?
906                    .into();
907                Ok((transaction.into(), metadata.into()))
908            }
909        }
910        .map(|(transaction, metadata)| Checked::basic(transaction, metadata))
911    }
912}
913
914impl From<ValidityError> for CheckError {
915    fn from(value: ValidityError) -> Self {
916        CheckError::Validity(value)
917    }
918}
919
920impl From<PredicateVerificationFailed> for CheckError {
921    fn from(value: PredicateVerificationFailed) -> Self {
922        CheckError::PredicateVerificationFailed(value)
923    }
924}
925
926#[cfg(feature = "random")]
927#[allow(non_snake_case)]
928#[allow(clippy::arithmetic_side_effects, clippy::cast_possible_truncation)]
929#[cfg(test)]
930mod tests {
931
932    use super::*;
933    use alloc::vec;
934    use fuel_asm::op;
935    use fuel_crypto::SecretKey;
936    use fuel_tx::{
937        field::{
938            ScriptGasLimit,
939            Tip,
940            WitnessLimit,
941            Witnesses,
942        },
943        Script,
944        TransactionBuilder,
945        ValidityError,
946    };
947    use fuel_types::canonical::Serialize;
948    use quickcheck::TestResult;
949    use quickcheck_macros::quickcheck;
950    use rand::{
951        rngs::StdRng,
952        Rng,
953        SeedableRng,
954    };
955
956    fn params(factor: u64) -> ConsensusParameters {
957        ConsensusParameters::new(
958            TxParameters::default(),
959            PredicateParameters::default(),
960            ScriptParameters::default(),
961            ContractParameters::default(),
962            FeeParameters::default().with_gas_price_factor(factor),
963            Default::default(),
964            Default::default(),
965            Default::default(),
966            Default::default(),
967            Default::default(),
968            Default::default(),
969        )
970    }
971
972    #[test]
973    fn into_checked__tx_accepts_valid_tx() {
974        // simple smoke test that valid txs can be checked
975        let rng = &mut StdRng::seed_from_u64(2322u64);
976        let gas_limit = 1000;
977        let input_amount = 1000;
978        let output_amount = 10;
979        let max_fee_limit = 500;
980        let tx =
981            valid_coin_tx(rng, gas_limit, input_amount, output_amount, max_fee_limit);
982
983        let checked = tx
984            .clone()
985            .into_checked(Default::default(), &ConsensusParameters::standard())
986            .expect("Expected valid transaction");
987
988        // verify transaction getter works
989        assert_eq!(checked.transaction(), &tx);
990        // verify available balance was decreased by max fee
991        assert_eq!(
992            checked.metadata().non_retryable_balances[&AssetId::default()],
993            input_amount - max_fee_limit - output_amount
994        );
995    }
996
997    #[test]
998    fn into_checked__tx_accepts_valid_signed_message_coin_for_fees() {
999        // simple test to ensure a tx that only has a message input can cover fees
1000        let rng = &mut StdRng::seed_from_u64(2322u64);
1001        let input_amount = 1000;
1002        let gas_limit = 1000;
1003        let zero_fee_limit = 500;
1004        let tx = signed_message_coin_tx(rng, gas_limit, input_amount, zero_fee_limit);
1005
1006        let checked = tx
1007            .into_checked(Default::default(), &ConsensusParameters::standard())
1008            .expect("Expected valid transaction");
1009
1010        // verify available balance was decreased by max fee
1011        assert_eq!(
1012            checked.metadata().non_retryable_balances[&AssetId::default()],
1013            input_amount - checked.transaction.max_fee_limit()
1014        );
1015    }
1016
1017    #[test]
1018    fn into_checked__tx_excludes_message_output_amount_from_fee() {
1019        // ensure message outputs aren't deducted from available balance
1020        let rng = &mut StdRng::seed_from_u64(2322u64);
1021        let input_amount = 100;
1022        let gas_limit = 1000;
1023        let zero_fee_limit = 50;
1024        let tx = signed_message_coin_tx(rng, gas_limit, input_amount, zero_fee_limit);
1025
1026        let checked = tx
1027            .into_checked(Default::default(), &ConsensusParameters::standard())
1028            .expect("Expected valid transaction");
1029
1030        // verify available balance was decreased by max fee
1031        assert_eq!(
1032            checked.metadata().non_retryable_balances[&AssetId::default()],
1033            input_amount - checked.transaction.max_fee_limit()
1034        );
1035    }
1036
1037    #[test]
1038    fn into_checked__message_data_signed_message_is_not_used_to_cover_fees() {
1039        let rng = &mut StdRng::seed_from_u64(2322u64);
1040
1041        // given
1042        let input_amount = 100;
1043
1044        // when
1045        let max_fee = input_amount;
1046        let tx = TransactionBuilder::script(vec![], vec![])
1047            .max_fee_limit(max_fee)
1048            // Add message input with enough to cover max fee
1049            .add_unsigned_message_input(SecretKey::random(rng), rng.gen(), rng.gen(), input_amount, vec![0xff; 10])
1050            // Add empty base coin
1051            .add_unsigned_coin_input(SecretKey::random(rng), rng.gen(), 0, AssetId::BASE, rng.gen())
1052            .finalize();
1053
1054        let err = tx
1055            .into_checked(Default::default(), &ConsensusParameters::standard())
1056            .expect_err("Expected valid transaction");
1057
1058        // then
1059        assert!(matches!(
1060            err,
1061            CheckError::Validity(ValidityError::InsufficientFeeAmount {
1062                expected: _,
1063                provided: 0
1064            })
1065        ));
1066    }
1067
1068    #[test]
1069    fn message_data_predicate_message_is_not_used_to_cover_fees() {
1070        let rng = &mut StdRng::seed_from_u64(2322u64);
1071        let gas_limit = 1000;
1072
1073        // given
1074        let input_amount = 100;
1075
1076        // when
1077        let max_fee = input_amount;
1078
1079        let tx = TransactionBuilder::script(vec![], vec![])
1080            .max_fee_limit(max_fee)
1081            .script_gas_limit(gas_limit)
1082            .add_input(Input::message_data_predicate(
1083                rng.gen(),
1084                rng.gen(),
1085                input_amount,
1086                rng.gen(),
1087                Default::default(),
1088                vec![0xff; 10],
1089                vec![0xaa; 10],
1090                vec![0xbb; 10],
1091            ))
1092            // Add empty base coin
1093            .add_unsigned_coin_input(SecretKey::random(rng), rng.gen(), 0, AssetId::BASE, rng.gen())
1094            .finalize();
1095
1096        let err = tx
1097            .into_checked(Default::default(), &ConsensusParameters::standard())
1098            .expect_err("Expected valid transaction");
1099
1100        // then
1101        assert!(matches!(
1102            err,
1103            CheckError::Validity(ValidityError::InsufficientFeeAmount {
1104                expected: _,
1105                provided: 0
1106            })
1107        ));
1108    }
1109
1110    // use quickcheck to fuzz any rounding or precision errors in the max fee w/ coin
1111    // input
1112    #[quickcheck]
1113    fn max_fee_coin_input(
1114        gas_price: u64,
1115        gas_limit: u64,
1116        witness_limit: u64,
1117        input_amount: u64,
1118        gas_price_factor: u64,
1119        seed: u64,
1120    ) -> TestResult {
1121        // verify max fee a transaction can consume based on gas limit + bytes is correct
1122
1123        // dont divide by zero
1124        if gas_price_factor == 0 {
1125            return TestResult::discard();
1126        }
1127
1128        let rng = &mut StdRng::seed_from_u64(seed);
1129        let gas_costs = GasCosts::default();
1130        let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(gas_price_factor);
1131        let predicate_gas_used = rng.gen();
1132        let tx = predicate_tx(
1133            rng,
1134            gas_limit,
1135            witness_limit,
1136            input_amount,
1137            predicate_gas_used,
1138        );
1139
1140        if let Ok(valid) = is_valid_max_fee(&tx, gas_price, &gas_costs, &fee_params) {
1141            TestResult::from_bool(valid)
1142        } else {
1143            TestResult::discard()
1144        }
1145    }
1146
1147    // use quickcheck to fuzz any rounding or precision errors in the min fee w/ coin
1148    // input
1149    #[quickcheck]
1150    fn min_fee_coin_input(
1151        gas_price: u64,
1152        gas_limit: u64,
1153        witness_limit: u64,
1154        input_amount: u64,
1155        gas_price_factor: u64,
1156        seed: u64,
1157    ) -> TestResult {
1158        // verify min fee a transaction can consume based on bytes is correct
1159
1160        // dont divide by zero
1161        if gas_price_factor == 0 {
1162            return TestResult::discard();
1163        }
1164        let rng = &mut StdRng::seed_from_u64(seed);
1165        let gas_costs = GasCosts::default();
1166        let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(gas_price_factor);
1167        let predicate_gas_used = rng.gen();
1168        let tx = predicate_tx(
1169            rng,
1170            gas_limit,
1171            witness_limit,
1172            input_amount,
1173            predicate_gas_used,
1174        );
1175
1176        if let Ok(valid) = is_valid_max_fee(&tx, gas_price, &gas_costs, &fee_params) {
1177            TestResult::from_bool(valid)
1178        } else {
1179            TestResult::discard()
1180        }
1181    }
1182
1183    // use quickcheck to fuzz any rounding or precision errors in the max fee w/ message
1184    // input
1185    #[quickcheck]
1186    fn max_fee_message_input(
1187        gas_price: u64,
1188        gas_limit: u64,
1189        input_amount: u64,
1190        gas_price_factor: u64,
1191        tip: u64,
1192        seed: u64,
1193    ) -> TestResult {
1194        // dont divide by zero
1195        if gas_price_factor == 0 {
1196            return TestResult::discard();
1197        }
1198
1199        let rng = &mut StdRng::seed_from_u64(seed);
1200        let gas_costs = GasCosts::default();
1201        let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(gas_price_factor);
1202        let tx = predicate_message_coin_tx(rng, gas_limit, input_amount, tip);
1203
1204        if let Ok(valid) = is_valid_max_fee(&tx, gas_price, &gas_costs, &fee_params) {
1205            TestResult::from_bool(valid)
1206        } else {
1207            TestResult::discard()
1208        }
1209    }
1210
1211    // use quickcheck to fuzz any rounding or precision errors in refund calculation
1212    #[quickcheck]
1213    fn refund_when_used_gas_is_zero(
1214        gas_price: u64,
1215        gas_limit: u64,
1216        input_amount: u64,
1217        gas_price_factor: u64,
1218        seed: u64,
1219        tip: u64,
1220    ) -> TestResult {
1221        // dont divide by zero
1222        if gas_price_factor == 0 {
1223            return TestResult::discard();
1224        }
1225
1226        let rng = &mut StdRng::seed_from_u64(seed);
1227        let gas_costs = GasCosts::default();
1228        let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(gas_price_factor);
1229        let tx = predicate_message_coin_tx(rng, gas_limit, input_amount, tip);
1230
1231        // Given
1232        let used_gas = 0;
1233
1234        // When
1235        let refund = tx.refund_fee(&gas_costs, &fee_params, used_gas, gas_price);
1236
1237        let min_fee = tx.min_fee(&gas_costs, &fee_params, gas_price);
1238        let max_fee = tx.max_fee(&gas_costs, &fee_params, gas_price);
1239
1240        // Then
1241        if let Some(refund) = refund {
1242            TestResult::from_bool(max_fee - min_fee == refund as u128)
1243        } else {
1244            TestResult::discard()
1245        }
1246    }
1247
1248    // use quickcheck to fuzz any rounding or precision errors in the min fee w/ message
1249    // input
1250    #[quickcheck]
1251    fn min_fee_message_input(
1252        gas_limit: u64,
1253        input_amount: u64,
1254        gas_price: u64,
1255        gas_price_factor: u64,
1256        tip: u64,
1257        seed: u64,
1258    ) -> TestResult {
1259        // verify min fee a transaction can consume based on bytes is correct
1260
1261        // dont divide by zero
1262        if gas_price_factor == 0 {
1263            return TestResult::discard();
1264        }
1265        let rng = &mut StdRng::seed_from_u64(seed);
1266        let gas_costs = GasCosts::default();
1267        let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(gas_price_factor);
1268        let tx = predicate_message_coin_tx(rng, gas_limit, input_amount, tip);
1269
1270        if let Ok(valid) = is_valid_min_fee(&tx, &gas_costs, &fee_params, gas_price) {
1271            TestResult::from_bool(valid)
1272        } else {
1273            TestResult::discard()
1274        }
1275    }
1276
1277    #[test]
1278    fn fee_multiple_signed_inputs() {
1279        let rng = &mut StdRng::seed_from_u64(2322u64);
1280        let gas_price = 100;
1281        let gas_limit = 1000;
1282        let gas_costs = GasCosts::default();
1283        let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(1);
1284        let tx = TransactionBuilder::script(vec![], vec![])
1285            .script_gas_limit(gas_limit)
1286            // Set up 3 signed inputs
1287            .add_unsigned_message_input(
1288                SecretKey::random(rng),
1289                rng.gen(),
1290                rng.gen(),
1291                rng.gen::<u32>() as u64,
1292                vec![],
1293            )
1294            .add_unsigned_message_input(
1295                SecretKey::random(rng),
1296                rng.gen(),
1297                rng.gen(),
1298                rng.gen::<u32>() as u64,
1299                vec![],
1300            )
1301            .add_unsigned_message_input(
1302                SecretKey::random(rng),
1303                rng.gen(),
1304                rng.gen(),
1305                rng.gen::<u32>() as u64,
1306                vec![],
1307            )
1308            .finalize();
1309        let fee =
1310            TransactionFee::checked_from_tx(&gas_costs, &fee_params, &tx, gas_price)
1311                .unwrap();
1312
1313        let min_fee = fee.min_fee();
1314        let expected_min_fee = (tx.metered_bytes_size() as u64
1315            * fee_params.gas_per_byte()
1316            + gas_costs.vm_initialization().resolve(tx.size() as u64)
1317            + 3 * gas_costs.eck1()
1318            + gas_costs.s256().resolve(tx.size() as u64))
1319            * gas_price;
1320        assert_eq!(min_fee, expected_min_fee);
1321
1322        let max_fee = fee.max_fee();
1323        let expected_max_fee = expected_min_fee + gas_limit * gas_price;
1324        assert_eq!(max_fee, expected_max_fee);
1325    }
1326
1327    #[test]
1328    fn fee_multiple_signed_inputs_single_owner() {
1329        let rng = &mut StdRng::seed_from_u64(2322u64);
1330        let gas_price = 100;
1331        let gas_limit = 1000;
1332        let gas_costs = GasCosts::default();
1333        let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(1);
1334        let secret = SecretKey::random(rng);
1335        let tx = TransactionBuilder::script(vec![], vec![])
1336            .script_gas_limit(gas_limit)
1337            // Set up 3 signed inputs
1338            .add_unsigned_message_input(
1339                secret,
1340                rng.gen(),
1341                rng.gen(),
1342                rng.gen::<u32>() as u64,
1343                vec![],
1344            )
1345            .add_unsigned_message_input(
1346                secret,
1347                rng.gen(),
1348                rng.gen(),
1349                rng.gen::<u32>() as u64,
1350                vec![],
1351            )
1352            .add_unsigned_message_input(
1353                secret,
1354                rng.gen(),
1355                rng.gen(),
1356                rng.gen::<u32>() as u64,
1357                vec![],
1358            )
1359            .finalize();
1360        let fee =
1361            TransactionFee::checked_from_tx(&gas_costs, &fee_params, &tx, gas_price)
1362                .unwrap();
1363
1364        let min_fee = fee.min_fee();
1365        // Because all inputs are owned by the same address, the address will only need to
1366        // be recovered once. Therefore, we charge only once for the address
1367        // recovery of the signed inputs.
1368        let expected_min_fee = (tx.metered_bytes_size() as u64
1369            * fee_params.gas_per_byte()
1370            + gas_costs.vm_initialization().resolve(tx.size() as u64)
1371            + gas_costs.eck1()
1372            + gas_costs.s256().resolve(tx.size() as u64))
1373            * gas_price;
1374        assert_eq!(min_fee, expected_min_fee);
1375
1376        let max_fee = fee.max_fee();
1377        let expected_max_fee = min_fee + gas_limit * gas_price;
1378        assert_eq!(max_fee, expected_max_fee);
1379    }
1380
1381    fn random_bytes<const N: usize, R: Rng + ?Sized>(rng: &mut R) -> Box<[u8; N]> {
1382        let mut bytes = Box::new([0u8; N]);
1383        for chunk in bytes.chunks_mut(32) {
1384            rng.fill(chunk);
1385        }
1386        bytes
1387    }
1388
1389    #[test]
1390    fn min_fee_multiple_predicate_inputs() {
1391        let rng = &mut StdRng::seed_from_u64(2322u64);
1392        let gas_price = 100;
1393        let gas_limit = 1000;
1394        let gas_costs = GasCosts::default();
1395        let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(1);
1396        let predicate_1 = random_bytes::<1024, _>(rng);
1397        let predicate_2 = random_bytes::<2048, _>(rng);
1398        let predicate_3 = random_bytes::<4096, _>(rng);
1399        let tx = TransactionBuilder::script(vec![], vec![])
1400            .script_gas_limit(gas_limit)
1401            // Set up 3 predicate inputs
1402            .add_input(Input::message_coin_predicate(
1403                rng.gen(),
1404                rng.gen(),
1405                rng.gen(),
1406                rng.gen(),
1407                50,
1408                predicate_1.to_vec(),
1409                vec![],
1410            ))
1411            .add_input(Input::message_coin_predicate(
1412                rng.gen(),
1413                rng.gen(),
1414                rng.gen(),
1415                rng.gen(),
1416                100,
1417                predicate_2.to_vec(),
1418                vec![],
1419            ))
1420            .add_input(Input::message_coin_predicate(
1421                rng.gen(),
1422                rng.gen(),
1423                rng.gen(),
1424                rng.gen(),
1425                200,
1426                predicate_3.to_vec(),
1427                vec![],
1428            ))
1429            .finalize();
1430        let fee =
1431            TransactionFee::checked_from_tx(&gas_costs, &fee_params, &tx, gas_price)
1432                .unwrap();
1433
1434        let min_fee = fee.min_fee();
1435        let expected_min_fee = (tx.size() as u64 * fee_params.gas_per_byte()
1436            + gas_costs.vm_initialization().resolve(tx.size() as u64)
1437            + gas_costs.contract_root().resolve(predicate_1.len() as u64)
1438            + gas_costs.contract_root().resolve(predicate_2.len() as u64)
1439            + gas_costs.contract_root().resolve(predicate_3.len() as u64)
1440            + 3 * gas_costs.vm_initialization().resolve(tx.size() as u64)
1441            + 50
1442            + 100
1443            + 200
1444            + gas_costs.s256().resolve(tx.size() as u64))
1445            * gas_price;
1446        assert_eq!(min_fee, expected_min_fee);
1447
1448        let max_fee = fee.max_fee();
1449        let expected_max_fee = min_fee + gas_limit * gas_price;
1450        assert_eq!(max_fee, expected_max_fee);
1451    }
1452
1453    #[test]
1454    fn min_fee_multiple_signed_and_predicate_inputs() {
1455        let rng = &mut StdRng::seed_from_u64(2322u64);
1456        let gas_price = 100;
1457        let gas_limit = 1000;
1458        let gas_costs = GasCosts::default();
1459        let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(1);
1460        let predicate_1 = random_bytes::<1024, _>(rng);
1461        let predicate_2 = random_bytes::<2048, _>(rng);
1462        let predicate_3 = random_bytes::<4096, _>(rng);
1463        let tx = TransactionBuilder::script(vec![], vec![])
1464            .script_gas_limit(gas_limit)
1465            // Set up 3 signed inputs
1466            .add_unsigned_message_input(
1467                SecretKey::random(rng),
1468                rng.gen(),
1469                rng.gen(),
1470                rng.gen::<u32>() as u64,
1471                vec![],
1472            )
1473            .add_unsigned_message_input(
1474                SecretKey::random(rng),
1475                rng.gen(),
1476                rng.gen(),
1477                rng.gen::<u32>() as u64,
1478                vec![],
1479            )
1480            .add_unsigned_message_input(
1481                SecretKey::random(rng),
1482                rng.gen(),
1483                rng.gen(),
1484                rng.gen::<u32>() as u64,
1485                vec![],
1486            )
1487            // Set up 3 predicate inputs
1488            .add_input(Input::message_coin_predicate(
1489                rng.gen(),
1490                rng.gen(),
1491                rng.gen(),
1492                rng.gen(),
1493                50,
1494                predicate_1.to_vec(),
1495                vec![],
1496            ))
1497            .add_input(Input::message_coin_predicate(
1498                rng.gen(),
1499                rng.gen(),
1500                rng.gen(),
1501                rng.gen(),
1502                100,
1503                predicate_2.to_vec(),
1504                vec![],
1505            ))
1506            .add_input(Input::message_coin_predicate(
1507                rng.gen(),
1508                rng.gen(),
1509                rng.gen(),
1510                rng.gen(),
1511                200,
1512                predicate_3.to_vec(),
1513                vec![],
1514            ))
1515            .finalize();
1516        let fee =
1517            TransactionFee::checked_from_tx(&gas_costs, &fee_params, &tx, gas_price)
1518                .unwrap();
1519
1520        let min_fee = fee.min_fee();
1521        let expected_min_fee = (tx.metered_bytes_size() as u64
1522            * fee_params.gas_per_byte()
1523            + 3 * gas_costs.eck1()
1524            + gas_costs.vm_initialization().resolve(tx.size() as u64)
1525            + gas_costs.contract_root().resolve(predicate_1.len() as u64)
1526            + gas_costs.contract_root().resolve(predicate_2.len() as u64)
1527            + gas_costs.contract_root().resolve(predicate_3.len() as u64)
1528            + 3 * gas_costs.vm_initialization().resolve(tx.size() as u64)
1529            + 50
1530            + 100
1531            + 200
1532            + gas_costs.s256().resolve(tx.size() as u64))
1533            * gas_price;
1534        assert_eq!(min_fee, expected_min_fee);
1535
1536        let max_fee = fee.max_fee();
1537        let expected_max_fee = min_fee + gas_limit * gas_price;
1538        assert_eq!(max_fee, expected_max_fee);
1539    }
1540
1541    #[test]
1542    fn fee_create_tx() {
1543        let rng = &mut StdRng::seed_from_u64(2322u64);
1544        let gas_price = 100;
1545        let witness_limit = 1000;
1546        let gas_costs = GasCosts::default();
1547        let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(1);
1548        let gen_storage_slot = || rng.gen::<StorageSlot>();
1549        let storage_slots = core::iter::repeat_with(gen_storage_slot)
1550            .take(100)
1551            .collect::<Vec<_>>();
1552        let storage_slots_len = storage_slots.len();
1553        let bytecode = rng.gen::<Witness>();
1554        let bytecode_len = bytecode.as_ref().len();
1555        let salt = rng.gen::<Salt>();
1556        let tx = TransactionBuilder::create(bytecode.clone(), salt, storage_slots)
1557            .witness_limit(witness_limit)
1558            .finalize();
1559        let fee =
1560            TransactionFee::checked_from_tx(&gas_costs, &fee_params, &tx, gas_price)
1561                .unwrap();
1562
1563        let min_fee = fee.min_fee();
1564        let expected_min_fee = (tx.metered_bytes_size() as u64
1565            * fee_params.gas_per_byte()
1566            + gas_costs.state_root().resolve(storage_slots_len as Word)
1567            + gas_costs.contract_root().resolve(bytecode_len as Word)
1568            + gas_costs.vm_initialization().resolve(tx.size() as u64)
1569            + gas_costs.s256().resolve(100)
1570            + gas_costs.s256().resolve(tx.size() as u64))
1571            * gas_price;
1572        assert_eq!(min_fee, expected_min_fee);
1573
1574        let max_fee = fee.max_fee();
1575        let expected_max_fee = min_fee
1576            + (witness_limit - bytecode.size() as u64)
1577                * fee_params.gas_per_byte()
1578                * gas_price;
1579        assert_eq!(max_fee, expected_max_fee);
1580    }
1581
1582    #[test]
1583    fn fee_create_tx_no_bytecode() {
1584        let rng = &mut StdRng::seed_from_u64(2322u64);
1585        let gas_price = 100;
1586        let witness_limit = 1000;
1587        let gas_costs = GasCosts::default();
1588        let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(1);
1589        let bytecode: Witness = Vec::<u8>::new().into();
1590        let salt = rng.gen::<Salt>();
1591        let tx = TransactionBuilder::create(bytecode.clone(), salt, vec![])
1592            .witness_limit(witness_limit)
1593            .finalize();
1594        let fee =
1595            TransactionFee::checked_from_tx(&gas_costs, &fee_params, &tx, gas_price)
1596                .unwrap();
1597
1598        let min_fee = fee.min_fee();
1599        let expected_min_fee = (tx.metered_bytes_size() as u64
1600            * fee_params.gas_per_byte()
1601            + gas_costs.state_root().resolve(0)
1602            + gas_costs.contract_root().resolve(0)
1603            + gas_costs.vm_initialization().resolve(tx.size() as u64)
1604            + gas_costs.s256().resolve(100)
1605            + gas_costs.s256().resolve(tx.size() as u64))
1606            * gas_price;
1607        assert_eq!(min_fee, expected_min_fee);
1608
1609        let max_fee = fee.max_fee();
1610        let expected_max_fee = min_fee
1611            + (witness_limit - bytecode.size_static() as u64)
1612                * fee_params.gas_per_byte()
1613                * gas_price;
1614        assert_eq!(max_fee, expected_max_fee);
1615    }
1616
1617    #[test]
1618    fn checked_tx_rejects_invalid_tx() {
1619        // simple smoke test that invalid txs cannot be checked
1620        let rng = &mut StdRng::seed_from_u64(2322u64);
1621        let asset = rng.gen();
1622        let gas_limit = 100;
1623        let input_amount = 1_000;
1624
1625        // create a tx with invalid signature
1626        let tx = TransactionBuilder::script(vec![], vec![])
1627            .script_gas_limit(gas_limit)
1628            .add_input(Input::coin_signed(
1629                rng.gen(),
1630                rng.gen(),
1631                input_amount,
1632                asset,
1633                rng.gen(),
1634                Default::default(),
1635            ))
1636            .add_input(Input::contract(
1637                rng.gen(),
1638                rng.gen(),
1639                rng.gen(),
1640                rng.gen(),
1641                rng.gen(),
1642            ))
1643            .add_output(Output::contract(1, rng.gen(), rng.gen()))
1644            .add_output(Output::coin(rng.gen(), 10, asset))
1645            .add_output(Output::change(rng.gen(), 0, asset))
1646            .add_witness(Default::default())
1647            .finalize();
1648
1649        let err = tx
1650            .into_checked(Default::default(), &ConsensusParameters::standard())
1651            .expect_err("Expected invalid transaction");
1652
1653        // assert that tx without base input assets fails
1654        assert!(matches!(
1655            err,
1656            CheckError::Validity(ValidityError::InputInvalidSignature { .. })
1657        ));
1658    }
1659
1660    #[test]
1661    fn into_checked__tx_fails_when_provided_fees_dont_cover_byte_costs() {
1662        let rng = &mut StdRng::seed_from_u64(2322u64);
1663
1664        let arb_input_amount = 1;
1665        let gas_price = 2; // price > amount
1666        let gas_limit = 0; // don't include any gas execution fees
1667        let factor = 1;
1668        let zero_max_fee = 0;
1669        let params = params(factor);
1670
1671        // setup "valid" transaction
1672        let transaction = base_asset_tx(rng, arb_input_amount, gas_limit, zero_max_fee);
1673        transaction
1674            .clone()
1675            .into_checked(Default::default(), &params)
1676            .unwrap();
1677        let fees = TransactionFee::checked_from_tx(
1678            &GasCosts::default(),
1679            params.fee_params(),
1680            &transaction,
1681            gas_price,
1682        )
1683        .unwrap();
1684        let real_max_fee = fees.max_fee();
1685
1686        let new_input_amount = real_max_fee;
1687        let mut new_transaction =
1688            base_asset_tx(rng, new_input_amount, gas_limit, real_max_fee);
1689        new_transaction
1690            .clone()
1691            .into_checked(Default::default(), &params)
1692            .unwrap()
1693            .into_ready(gas_price, &GasCosts::default(), params.fee_params(), None)
1694            .expect("`new_transaction` should be fully valid");
1695
1696        // given
1697        // invalidating the transaction by increasing witness size
1698        new_transaction.witnesses_mut().push(rng.gen());
1699        let bigger_checked = new_transaction
1700            .into_checked(Default::default(), &params)
1701            .unwrap();
1702
1703        // when
1704        let err = bigger_checked
1705            .into_ready(gas_price, &GasCosts::default(), params.fee_params(), None)
1706            .expect_err("Expected invalid transaction");
1707
1708        let max_fee_from_policies = match err {
1709            CheckError::InsufficientMaxFee {
1710                max_fee_from_policies,
1711                ..
1712            } => max_fee_from_policies,
1713            _ => panic!("expected insufficient max fee; found {err:?}"),
1714        };
1715
1716        // then
1717        assert_eq!(max_fee_from_policies, real_max_fee);
1718    }
1719
1720    #[test]
1721    fn into_checked__tx_fails_when_provided_fees_dont_cover_fee_limit() {
1722        let rng = &mut StdRng::seed_from_u64(2322u64);
1723
1724        let input_amount = 10;
1725        let factor = 1;
1726        // make gas price too high for the input amount
1727        let gas_limit = input_amount + 1; // make gas cost 1 higher than input amount
1728
1729        // given
1730        let input_amount = 10;
1731        let big_fee_limit = input_amount + 1;
1732
1733        let transaction = base_asset_tx(rng, input_amount, gas_limit, big_fee_limit);
1734
1735        let consensus_params = params(factor);
1736
1737        // when
1738        let err = transaction
1739            .into_checked(Default::default(), &consensus_params)
1740            .expect_err("overflow expected");
1741
1742        // then
1743        let provided = match err {
1744            CheckError::Validity(ValidityError::InsufficientFeeAmount {
1745                provided,
1746                ..
1747            }) => provided,
1748            _ => panic!("expected insufficient fee amount; found {err:?}"),
1749        };
1750        assert_eq!(provided, input_amount);
1751    }
1752
1753    #[test]
1754    fn into_ready__bytes_fee_cant_overflow() {
1755        let rng = &mut StdRng::seed_from_u64(2322u64);
1756
1757        let input_amount = 1000;
1758        let max_gas_price = Word::MAX;
1759        let gas_limit = 0; // ensure only bytes are included in fee
1760        let zero_fee_limit = 0;
1761        let transaction = base_asset_tx(rng, input_amount, gas_limit, zero_fee_limit);
1762        let gas_costs = GasCosts::default();
1763
1764        let consensus_params = params(1);
1765
1766        let fee_params = consensus_params.fee_params();
1767        let err = transaction
1768            .into_checked(Default::default(), &consensus_params)
1769            .unwrap()
1770            .into_ready(max_gas_price, &gas_costs, fee_params, None)
1771            .expect_err("overflow expected");
1772
1773        assert_eq!(err, CheckError::Validity(ValidityError::BalanceOverflow));
1774    }
1775
1776    #[test]
1777    fn into_ready__fails_if_fee_limit_too_low() {
1778        let rng = &mut StdRng::seed_from_u64(2322u64);
1779
1780        let input_amount = 1000;
1781        let gas_price = 100;
1782        let gas_limit = 0; // ensure only bytes are included in fee
1783        let gas_costs = GasCosts::default();
1784
1785        let consensus_params = params(1);
1786
1787        let fee_params = consensus_params.fee_params();
1788
1789        // given
1790        let zero_fee_limit = 0;
1791        let transaction = base_asset_tx(rng, input_amount, gas_limit, zero_fee_limit);
1792
1793        // when
1794        let err = transaction
1795            .into_checked(Default::default(), &consensus_params)
1796            .unwrap()
1797            .into_ready(gas_price, &gas_costs, fee_params, None)
1798            .expect_err("overflow expected");
1799
1800        // then
1801        assert!(matches!(err, CheckError::InsufficientMaxFee { .. }));
1802    }
1803
1804    #[test]
1805    fn into_ready__tx_fails_if_tip_not_covered() {
1806        let rng = &mut StdRng::seed_from_u64(2322u64);
1807
1808        // tx without tip and fee limit that is good
1809        let input_amount = 1;
1810        let gas_limit = 1000;
1811        let params = ConsensusParameters::standard();
1812        let block_height = 1.into();
1813        let gas_costs = GasCosts::default();
1814        let max_fee_limit = input_amount;
1815        let gas_price = 1;
1816
1817        let tx_without_tip =
1818            base_asset_tx_with_tip(rng, input_amount, gas_limit, max_fee_limit, None);
1819        tx_without_tip
1820            .clone()
1821            .into_checked(block_height, &params)
1822            .unwrap()
1823            .into_ready(gas_price, &gas_costs, params.fee_params(), None)
1824            .expect("Should be valid");
1825
1826        // given
1827        let tip = 100;
1828        let tx_without_enough_to_pay_for_tip = base_asset_tx_with_tip(
1829            rng,
1830            input_amount,
1831            gas_limit,
1832            max_fee_limit,
1833            Some(tip),
1834        );
1835        tx_without_enough_to_pay_for_tip
1836            .into_checked(block_height, &params)
1837            .unwrap()
1838            .into_ready(gas_price, &gas_costs, params.fee_params(), None)
1839            .expect_err("Expected invalid transaction");
1840
1841        // when
1842        let new_input_amount = input_amount + tip;
1843        let new_gas_limit = new_input_amount;
1844        let tx = base_asset_tx_with_tip(
1845            rng,
1846            new_input_amount,
1847            gas_limit,
1848            new_gas_limit,
1849            Some(tip),
1850        );
1851
1852        // then
1853        tx.clone()
1854            .into_checked(block_height, &params)
1855            .unwrap()
1856            .into_ready(gas_price, &GasCosts::default(), params.fee_params(), None)
1857            .expect("Should be valid");
1858    }
1859
1860    #[test]
1861    fn into_ready__return_overflow_error_if_gas_price_too_high() {
1862        let rng = &mut StdRng::seed_from_u64(2322u64);
1863        let input_amount = 1000;
1864        let gas_price = Word::MAX;
1865        let gas_limit = 2; // 2 * max should cause gas fee overflow
1866        let max_fee_limit = 0;
1867
1868        let transaction = base_asset_tx(rng, input_amount, gas_limit, max_fee_limit);
1869
1870        let consensus_params = params(1);
1871
1872        let err = transaction
1873            .into_checked(Default::default(), &consensus_params)
1874            .unwrap()
1875            .into_ready(
1876                gas_price,
1877                &GasCosts::default(),
1878                consensus_params.fee_params(),
1879                None,
1880            )
1881            .expect_err("overflow expected");
1882
1883        assert_eq!(err, CheckError::Validity(ValidityError::BalanceOverflow));
1884    }
1885
1886    #[test]
1887    fn checked_tx_fails_if_asset_is_overspent_by_coin_output() {
1888        let input_amount = 1_000;
1889        let rng = &mut StdRng::seed_from_u64(2322u64);
1890        let secret = SecretKey::random(rng);
1891        let any_asset = rng.gen();
1892        let tx = TransactionBuilder::script(vec![], vec![])
1893            .script_gas_limit(100)
1894            // base asset
1895            .add_unsigned_coin_input(
1896                secret,
1897                rng.gen(),
1898                input_amount,
1899                AssetId::default(),
1900                rng.gen(),
1901            )
1902            .add_output(Output::change(rng.gen(), 0, AssetId::default()))
1903            // arbitrary spending asset
1904            .add_unsigned_coin_input(
1905                secret,
1906                rng.gen(),
1907                input_amount,
1908                any_asset,
1909                rng.gen(),
1910            )
1911            .add_output(Output::coin(rng.gen(), input_amount + 1, any_asset))
1912            .add_output(Output::change(rng.gen(), 0, any_asset))
1913            .finalize();
1914
1915        let checked = tx
1916            .into_checked(Default::default(), &ConsensusParameters::standard())
1917            .expect_err("Expected valid transaction");
1918
1919        assert_eq!(
1920            CheckError::Validity(ValidityError::InsufficientInputAmount {
1921                asset: any_asset,
1922                expected: input_amount + 1,
1923                provided: input_amount,
1924            }),
1925            checked
1926        );
1927    }
1928
1929    #[cfg(feature = "std")]
1930    #[test]
1931    fn basic_check_marks_basic_flag() {
1932        let block_height = 1.into();
1933
1934        let tx = Transaction::default_test_tx();
1935        // Sets Checks::Basic
1936        let checked = tx
1937            .into_checked_basic(block_height, &ConsensusParameters::standard())
1938            .unwrap();
1939        assert!(checked.checks().contains(Checks::Basic));
1940    }
1941
1942    #[test]
1943    fn signatures_check_marks_signatures_flag() {
1944        let mut rng = StdRng::seed_from_u64(1);
1945        let block_height = 1.into();
1946        let max_fee_limit = 0;
1947
1948        let tx = valid_coin_tx(&mut rng, 100000, 1000000, 10, max_fee_limit);
1949        let chain_id = ChainId::default();
1950        let checked = tx
1951            // Sets Checks::Basic
1952            .into_checked(
1953                block_height,
1954                &ConsensusParameters::standard_with_id(chain_id),
1955            )
1956            .unwrap()
1957            // Sets Checks::Signatures
1958            .check_signatures(&chain_id)
1959            .unwrap();
1960
1961        assert!(checked
1962            .checks()
1963            .contains(Checks::Basic | Checks::Signatures));
1964    }
1965
1966    #[test]
1967    fn predicates_check_marks_predicate_flag() {
1968        let mut rng = StdRng::seed_from_u64(1);
1969        let block_height = 1.into();
1970        let gas_costs = GasCosts::default();
1971
1972        let tx = predicate_tx(&mut rng, 1000000, 1000000, 1000000, gas_costs.ret());
1973
1974        let mut consensus_params = ConsensusParameters::standard();
1975        consensus_params.set_gas_costs(gas_costs);
1976
1977        let check_predicate_params = CheckPredicateParams::from(&consensus_params);
1978
1979        let checked = tx
1980            // Sets Checks::Basic
1981            .into_checked(
1982                block_height,
1983                &consensus_params,
1984            )
1985            .unwrap()
1986            // Sets Checks::Predicates
1987            .check_predicates(&check_predicate_params, MemoryInstance::new(), &EmptyStorage)
1988            .unwrap();
1989        assert!(checked
1990            .checks()
1991            .contains(Checks::Basic | Checks::Predicates));
1992    }
1993
1994    fn is_valid_max_fee(
1995        tx: &Script,
1996        gas_price: u64,
1997        gas_costs: &GasCosts,
1998        fee_params: &FeeParameters,
1999    ) -> Result<bool, ValidityError> {
2000        fn gas_to_fee(gas: u64, price: u64, factor: u64) -> u128 {
2001            let prices_gas = gas as u128 * price as u128;
2002            let fee = prices_gas / factor as u128;
2003            let fee_remainder = (prices_gas.rem_euclid(factor as u128) > 0) as u128;
2004            fee + fee_remainder
2005        }
2006
2007        // cant overflow as metered bytes * gas_per_byte < u64::MAX
2008        let gas_used_by_bytes = fee_params
2009            .gas_per_byte()
2010            .saturating_mul(tx.metered_bytes_size() as u64);
2011        let gas_used_by_inputs = tx.gas_used_by_inputs(gas_costs);
2012        let gas_used_by_metadata = tx.gas_used_by_metadata(gas_costs);
2013        let min_gas = gas_used_by_bytes
2014            .saturating_add(gas_used_by_inputs)
2015            .saturating_add(gas_used_by_metadata)
2016            .saturating_add(
2017                gas_costs
2018                    .vm_initialization()
2019                    .resolve(tx.metered_bytes_size() as u64),
2020            );
2021
2022        // use different division mechanism than impl
2023        let witness_limit_allowance = tx
2024            .witness_limit()
2025            .saturating_sub(tx.witnesses().size_dynamic() as u64)
2026            .saturating_mul(fee_params.gas_per_byte());
2027        let max_gas = min_gas
2028            .saturating_add(*tx.script_gas_limit())
2029            .saturating_add(witness_limit_allowance);
2030        let max_fee = gas_to_fee(max_gas, gas_price, fee_params.gas_price_factor());
2031
2032        let max_fee_with_tip = max_fee.saturating_add(tx.tip() as u128);
2033
2034        let result = max_fee_with_tip == tx.max_fee(gas_costs, fee_params, gas_price);
2035        Ok(result)
2036    }
2037
2038    fn is_valid_min_fee<Tx>(
2039        tx: &Tx,
2040        gas_costs: &GasCosts,
2041        fee_params: &FeeParameters,
2042        gas_price: u64,
2043    ) -> Result<bool, ValidityError>
2044    where
2045        Tx: Chargeable + field::Inputs + field::Outputs,
2046    {
2047        // cant overflow as (metered bytes + gas_used_by_predicates) * gas_per_byte <
2048        // u64::MAX
2049        let gas_used_by_bytes = fee_params
2050            .gas_per_byte()
2051            .saturating_mul(tx.metered_bytes_size() as u64);
2052        let gas_used_by_inputs = tx.gas_used_by_inputs(gas_costs);
2053        let gas_used_by_metadata = tx.gas_used_by_metadata(gas_costs);
2054        let gas = gas_used_by_bytes
2055            .saturating_add(gas_used_by_inputs)
2056            .saturating_add(gas_used_by_metadata)
2057            .saturating_add(
2058                gas_costs
2059                    .vm_initialization()
2060                    .resolve(tx.metered_bytes_size() as u64),
2061            );
2062        let total = gas as u128 * gas_price as u128;
2063        // use different division mechanism than impl
2064        let fee = total / fee_params.gas_price_factor() as u128;
2065        let fee_remainder =
2066            (total.rem_euclid(fee_params.gas_price_factor() as u128) > 0) as u128;
2067        let rounded_fee = fee
2068            .saturating_add(fee_remainder)
2069            .saturating_add(tx.tip() as u128);
2070        let min_fee = rounded_fee;
2071        let calculated_min_fee = tx.min_fee(gas_costs, fee_params, gas_price);
2072
2073        Ok(min_fee == calculated_min_fee)
2074    }
2075
2076    fn valid_coin_tx(
2077        rng: &mut StdRng,
2078        gas_limit: u64,
2079        input_amount: u64,
2080        output_amount: u64,
2081        max_fee_limit: u64,
2082    ) -> Script {
2083        let asset = AssetId::default();
2084        TransactionBuilder::script(vec![], vec![])
2085            .script_gas_limit(gas_limit)
2086            .max_fee_limit(max_fee_limit)
2087            .add_unsigned_coin_input(
2088                SecretKey::random(rng),
2089                rng.gen(),
2090                input_amount,
2091                asset,
2092                rng.gen(),
2093            )
2094            .add_input(Input::contract(
2095                rng.gen(),
2096                rng.gen(),
2097                rng.gen(),
2098                rng.gen(),
2099                rng.gen(),
2100            ))
2101            .add_output(Output::contract(1, rng.gen(), rng.gen()))
2102            .add_output(Output::coin(rng.gen(), output_amount, asset))
2103            .add_output(Output::change(rng.gen(), 0, asset))
2104            .finalize()
2105    }
2106
2107    // used when proptesting to avoid expensive crypto signatures
2108    fn predicate_tx(
2109        rng: &mut StdRng,
2110        gas_limit: u64,
2111        witness_limit: u64,
2112        fee_input_amount: u64,
2113        predicate_gas_used: u64,
2114    ) -> Script {
2115        let asset = AssetId::default();
2116        let predicate = vec![op::ret(1)].into_iter().collect::<Vec<u8>>();
2117        let owner = Input::predicate_owner(&predicate);
2118        let zero_fee_limit = 0;
2119        TransactionBuilder::script(vec![], vec![])
2120            .max_fee_limit(zero_fee_limit)
2121            .script_gas_limit(gas_limit)
2122            .witness_limit(witness_limit)
2123            .add_input(Input::coin_predicate(
2124                rng.gen(),
2125                owner,
2126                fee_input_amount,
2127                asset,
2128                rng.gen(),
2129                predicate_gas_used,
2130                predicate,
2131                vec![],
2132            ))
2133            .add_output(Output::change(rng.gen(), 0, asset))
2134            .finalize()
2135    }
2136
2137    // used to verify message inputs can cover fees
2138    fn signed_message_coin_tx(
2139        rng: &mut StdRng,
2140        gas_limit: u64,
2141        input_amount: u64,
2142        max_fee: u64,
2143    ) -> Script {
2144        TransactionBuilder::script(vec![], vec![])
2145            .max_fee_limit(max_fee)
2146            .script_gas_limit(gas_limit)
2147            .add_unsigned_message_input(
2148                SecretKey::random(rng),
2149                rng.gen(),
2150                rng.gen(),
2151                input_amount,
2152                vec![],
2153            )
2154            .finalize()
2155    }
2156
2157    fn predicate_message_coin_tx(
2158        rng: &mut StdRng,
2159        gas_limit: u64,
2160        input_amount: u64,
2161        tip: u64,
2162    ) -> Script {
2163        TransactionBuilder::script(vec![], vec![])
2164            .tip(tip)
2165            .script_gas_limit(gas_limit)
2166            .add_input(Input::message_coin_predicate(
2167                rng.gen(),
2168                rng.gen(),
2169                input_amount,
2170                rng.gen(),
2171                Default::default(),
2172                vec![],
2173                vec![],
2174            ))
2175            .finalize()
2176    }
2177
2178    fn base_asset_tx(
2179        rng: &mut StdRng,
2180        input_amount: u64,
2181        gas_limit: u64,
2182        max_fee: u64,
2183    ) -> Script {
2184        base_asset_tx_with_tip(rng, input_amount, gas_limit, max_fee, None)
2185    }
2186
2187    fn base_asset_tx_with_tip(
2188        rng: &mut StdRng,
2189        input_amount: u64,
2190        gas_limit: u64,
2191        max_fee: u64,
2192        tip: Option<u64>,
2193    ) -> Script {
2194        let mut builder = TransactionBuilder::script(vec![], vec![]);
2195        if let Some(tip) = tip {
2196            builder.tip(tip);
2197        }
2198        builder
2199            .max_fee_limit(max_fee)
2200            .script_gas_limit(gas_limit)
2201            .add_unsigned_coin_input(
2202                SecretKey::random(rng),
2203                rng.gen(),
2204                input_amount,
2205                AssetId::default(),
2206                rng.gen(),
2207            )
2208            .add_output(Output::change(rng.gen(), 0, AssetId::default()))
2209            .finalize()
2210    }
2211}