Skip to main content

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