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