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    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    /// Possible types of transaction checks.
63    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
64    pub struct Checks: u32 {
65        /// Basic checks defined in the specification for each transaction:
66        /// https://github.com/FuelLabs/fuel-specs/blob/master/src/tx-format/transaction.md#transaction
67        /// Also ensures that malleable fields are zeroed.
68        const Basic         = 0b00000001;
69        /// Check that signature in the transactions are valid.
70        const Signatures    = 0b00000010;
71        /// Check that predicate in the transactions are valid.
72        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/// The type describes that the inner transaction was already checked.
83///
84/// All fields are private, and there is no constructor, so it is impossible to create the
85/// instance of `Checked` outside the `fuel-tx` crate.
86///
87/// The inner data is immutable to prevent modification to invalidate the checking.
88///
89/// If you need to modify an inner state, you need to get inner values
90/// (via the `Into<(Tx, Tx ::Metadata)>` trait), modify them and check again.
91///
92/// # Dev note: Avoid serde serialization of this type.
93///
94/// Since checked tx would need to be re-validated on deserialization anyways,
95/// it's cleaner to redo the tx check.
96#[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    /// Returns reference on inner transaction.
117    pub fn transaction(&self) -> &Tx {
118        &self.transaction
119    }
120
121    /// Returns the metadata generated during the check for transaction.
122    pub fn metadata(&self) -> &Tx::Metadata {
123        &self.metadata
124    }
125
126    /// Returns the bitmask of all passed checks.
127    pub fn checks(&self) -> &Checks {
128        &self.checks_bitmask
129    }
130
131    /// Performs check of signatures, if not yet done.
132    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/// Transaction that has checks for all dynamic values, e.g. `gas_price`
142#[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    /// Consume and decompose components of the `Immutable` transaction.
152    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    /// Getter for `gas_price` field
164    pub fn gas_price(&self) -> Word {
165        self.gas_price
166    }
167}
168
169#[cfg(feature = "test-helpers")]
170impl<Tx: IntoChecked> Checked<Tx> {
171    /// Convert `Checked` into `Ready` without performing final checks.
172    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    /// Run final checks on `Checked` using dynamic values, e.g. `gas_price`
189    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    /// Returns the transaction ID from the computed metadata
236    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/// The error can occur when transforming transactions into the `Checked` type.
287#[derive(Debug, Clone, PartialEq)]
288#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
289pub enum CheckError {
290    /// The transaction doesn't pass validity rules.
291    Validity(ValidityError),
292    /// The predicate verification failed.
293    PredicateVerificationFailed(PredicateVerificationFailed),
294    /// The max fee used during checking was lower than calculated during `Immutable`
295    /// conversion
296    InsufficientMaxFee {
297        /// The max fee from the policies defined by the user.
298        max_fee_from_policies: Word,
299        /// The max fee calculated from the gas price and gas used by the transaction.
300        max_fee_from_gas_price: Word,
301    },
302}
303
304/// Performs checks for a transaction
305pub trait IntoChecked: FormatValidityChecks + Sized {
306    /// Metadata produced during the check.
307    type Metadata: Sized;
308
309    /// Returns transaction that passed all `Checks`.
310    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    /// Returns transaction that passed all `Checks` accepting reusable memory
327    /// to run predicates.
328    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    /// Returns transaction that passed all `Checks`.
348    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    /// Returns transaction that passed all `Checks` accepting reusable memory
368    /// to run predicates.
369    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    /// Returns transaction that passed only `Checks::Basic`.
388    fn into_checked_basic(
389        self,
390        block_height: BlockHeight,
391        consensus_params: &ConsensusParameters,
392    ) -> Result<Checked<Self>, CheckError>;
393}
394
395/// The parameters needed for checking a predicate
396#[derive(Debug, Clone)]
397pub struct CheckPredicateParams {
398    /// Gas costs for opcodes
399    pub gas_costs: GasCosts,
400    /// Chain ID
401    pub chain_id: ChainId,
402    /// Maximum gas per predicate
403    pub max_gas_per_predicate: u64,
404    /// Maximum gas per transaction
405    pub max_gas_per_tx: u64,
406    /// Maximum number of inputs
407    pub max_inputs: u16,
408    /// Maximum size of the contract in bytes
409    pub contract_max_size: u64,
410    /// Maximum length of the message data
411    pub max_message_data_length: u64,
412    /// Offset of the transaction data in the memory
413    pub tx_offset: usize,
414    /// Fee parameters
415    pub fee_params: FeeParameters,
416    /// Base Asset ID
417    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/// Provides predicate verification functionality for the transaction.
451#[async_trait::async_trait]
452pub trait CheckPredicates: Sized {
453    /// Performs predicates verification of the transaction.
454    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    /// Performs predicates verification of the transaction in parallel.
463    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/// Provides predicate estimation functionality for the transaction.
476#[async_trait::async_trait]
477pub trait EstimatePredicates: Sized {
478    /// Estimates predicates of the transaction.
479    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    /// Estimates predicates of the transaction in parallel.
489    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    /// Estimates predicates of the transaction.
506    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    /// Estimates predicates of the transaction in parallel.
515    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/// Executes CPU-heavy tasks in parallel.
528#[async_trait::async_trait]
529pub trait ParallelExecutor {
530    /// Future created from a CPU-heavy task.
531    type Task: Future + Send + 'static;
532
533    /// Creates a Future from a CPU-heavy task.
534    fn create_task<F>(func: F) -> Self::Task
535    where
536        F: FnOnce() -> (usize, Result<Word, PredicateVerificationFailed>)
537            + Send
538            + 'static;
539
540    /// Executes tasks created by `create_task` in parallel.
541    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/// The Enum version of `Checked<Transaction>` allows getting the inner variant without
889/// losing "checked" status.
890///
891/// It is possible to freely convert `Checked<Transaction>` into `CheckedTransaction` and
892/// vice verse without the overhead.
893#[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        // # Dev note: Avoid wildcard pattern to be sure that all variants are covered.
913        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            // The code should produce the `CheckedMetadata` for the corresponding
933            // transaction variant. It is done in the implementation of the
934            // `IntoChecked` trait for `Transaction`. With the current
935            // implementation, the patterns below are unreachable.
936            (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/// The `IntoChecked` metadata for `CheckedTransaction`.
1020#[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        // simple smoke test that valid txs can be checked
1177        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        // verify transaction getter works
1191        assert_eq!(checked.transaction(), &tx);
1192        // verify available balance was decreased by max fee
1193        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        // simple test to ensure a tx that only has a message input can cover fees
1202        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        // verify available balance was decreased by max fee
1213        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        // ensure message outputs aren't deducted from available balance
1222        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        // verify available balance was decreased by max fee
1233        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        // given
1244        let input_amount = 100;
1245
1246        // when
1247        let max_fee = input_amount;
1248        let tx = TransactionBuilder::script(vec![], vec![])
1249            .max_fee_limit(max_fee)
1250            // Add message input with enough to cover max fee
1251            .add_unsigned_message_input(SecretKey::random(rng), rng.r#gen(), rng.r#gen(), input_amount, vec![0xff; 10])
1252            // Add empty base coin
1253            .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        // then
1261        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        // given
1276        let input_amount = 100;
1277
1278        // when
1279        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 empty base coin
1295            .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        // then
1303        assert!(matches!(
1304            err,
1305            CheckError::Validity(ValidityError::InsufficientFeeAmount {
1306                expected: _,
1307                provided: 0
1308            })
1309        ));
1310    }
1311
1312    // use quickcheck to fuzz any rounding or precision errors in the max fee w/ coin
1313    // input
1314    #[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        // verify max fee a transaction can consume based on gas limit + bytes is correct
1324
1325        // dont divide by zero
1326        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    // use quickcheck to fuzz any rounding or precision errors in the min fee w/ coin
1350    // input
1351    #[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        // verify min fee a transaction can consume based on bytes is correct
1361
1362        // dont divide by zero
1363        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    // use quickcheck to fuzz any rounding or precision errors in the max fee w/ message
1386    // input
1387    #[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        // dont divide by zero
1397        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    // use quickcheck to fuzz any rounding or precision errors in refund calculation
1414    #[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        // dont divide by zero
1424        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        // Given
1434        let used_gas = 0;
1435
1436        // When
1437        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        // Then
1443        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    // use quickcheck to fuzz any rounding or precision errors in the min fee w/ message
1451    // input
1452    #[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        // verify min fee a transaction can consume based on bytes is correct
1462
1463        // dont divide by zero
1464        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            // Set up 3 signed inputs
1489            .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            // Set up 3 signed inputs
1540            .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        // Because all inputs are owned by the same address, the address will only need to
1568        // be recovered once. Therefore, we charge only once for the address
1569        // recovery of the signed inputs.
1570        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            // Set up 3 predicate inputs
1604            .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            // Set up 3 signed inputs
1668            .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            // Set up 3 predicate inputs
1690            .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        // simple smoke test that invalid txs cannot be checked
1822        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        // create a tx with invalid signature
1828        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 that tx without base input assets fails
1856        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; // price > amount
1868        let gas_limit = 0; // don't include any gas execution fees
1869        let factor = 1;
1870        let zero_max_fee = 0;
1871        let params = params(factor);
1872
1873        // setup "valid" transaction
1874        let transaction = base_asset_tx(rng, arb_input_amount, gas_limit, zero_max_fee);
1875        transaction
1876            .clone()
1877            .into_checked(Default::default(), &params)
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(), &params)
1894            .unwrap()
1895            .into_ready(gas_price, &GasCosts::default(), params.fee_params(), None)
1896            .expect("`new_transaction` should be fully valid");
1897
1898        // given
1899        // invalidating the transaction by increasing witness size
1900        new_transaction.witnesses_mut().push(rng.r#gen());
1901        let bigger_checked = new_transaction
1902            .into_checked(Default::default(), &params)
1903            .unwrap();
1904
1905        // when
1906        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        // then
1919        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        // make gas price too high for the input amount
1929        let gas_limit = input_amount + 1; // make gas cost 1 higher than input amount
1930
1931        // given
1932        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        // when
1940        let err = transaction
1941            .into_checked(Default::default(), &consensus_params)
1942            .expect_err("overflow expected");
1943
1944        // then
1945        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; // ensure only bytes are included in fee
1962        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; // ensure only bytes are included in fee
1985        let gas_costs = GasCosts::default();
1986
1987        let consensus_params = params(1);
1988
1989        let fee_params = consensus_params.fee_params();
1990
1991        // given
1992        let zero_fee_limit = 0;
1993        let transaction = base_asset_tx(rng, input_amount, gas_limit, zero_fee_limit);
1994
1995        // when
1996        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        // then
2003        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        // tx without tip and fee limit that is good
2011        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, &params)
2024            .unwrap()
2025            .into_ready(gas_price, &gas_costs, params.fee_params(), None)
2026            .expect("Should be valid");
2027
2028        // given
2029        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, &params)
2039            .unwrap()
2040            .into_ready(gas_price, &gas_costs, params.fee_params(), None)
2041            .expect_err("Expected invalid transaction");
2042
2043        // when
2044        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        // then
2055        tx.clone()
2056            .into_checked(block_height, &params)
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; // 2 * max should cause gas fee overflow
2068        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            // base asset
2097            .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            // arbitrary spending asset
2106            .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        // Sets Checks::Basic
2138        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            // Sets Checks::Basic
2154            .into_checked(
2155                block_height,
2156                &ConsensusParameters::standard_with_id(chain_id),
2157            )
2158            .unwrap()
2159            // Sets Checks::Signatures
2160            .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            // Sets Checks::Basic
2185            .into_checked(
2186                block_height,
2187                &consensus_params,
2188            )
2189            .unwrap()
2190            // Sets Checks::Predicates
2191            .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        // cant overflow as metered bytes * gas_per_byte < u64::MAX
2214        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        // use different division mechanism than impl
2229        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        // cant overflow as (metered bytes + gas_used_by_predicates) * gas_per_byte <
2254        // u64::MAX
2255        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        // use different division mechanism than impl
2270        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    // used when proptesting to avoid expensive crypto signatures
2314    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    // used to verify message inputs can cover fees
2344    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}