#![allow(non_upper_case_globals)]
use fuel_tx::{
CheckError,
ConsensusParameters,
Create,
Mint,
Script,
Transaction,
};
use fuel_types::{
BlockHeight,
ChainId,
};
use core::borrow::Borrow;
use std::future::Future;
mod balances;
pub mod builder;
pub mod types;
pub use types::*;
use crate::{
checked_transaction::balances::{
initial_free_balances,
AvailableBalances,
},
error::PredicateVerificationFailed,
gas::GasCosts,
interpreter::{
CheckedMetadata as CheckedMetadataAccessTrait,
InitialBalances,
},
prelude::*,
};
bitflags::bitflags! {
pub struct Checks: u32 {
const Basic = 0b00000001;
const Signatures = 0b00000010;
const Predicates = 0b00000100;
const All = Self::Basic.bits
| Self::Signatures.bits
| Self::Predicates.bits;
}
}
impl core::fmt::Display for Checks {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{:032b}", self.bits)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Checked<Tx: IntoChecked> {
transaction: Tx,
metadata: Tx::Metadata,
checks_bitmask: Checks,
}
impl<Tx: IntoChecked> Checked<Tx> {
fn new(transaction: Tx, metadata: Tx::Metadata, checks_bitmask: Checks) -> Self {
Checked {
transaction,
metadata,
checks_bitmask,
}
}
pub(crate) fn basic(transaction: Tx, metadata: Tx::Metadata) -> Self {
Checked::new(transaction, metadata, Checks::Basic)
}
pub fn transaction(&self) -> &Tx {
&self.transaction
}
pub fn metadata(&self) -> &Tx::Metadata {
&self.metadata
}
pub fn checks(&self) -> &Checks {
&self.checks_bitmask
}
pub fn check_signatures(mut self, chain_id: &ChainId) -> Result<Self, CheckError> {
if !self.checks_bitmask.contains(Checks::Signatures) {
self.transaction.check_signatures(chain_id)?;
self.checks_bitmask.insert(Checks::Signatures);
}
Ok(self)
}
}
impl<Tx: IntoChecked + UniqueIdentifier> Checked<Tx> {
pub fn id(&self) -> TxId {
self.transaction
.cached_id()
.expect("Transaction metadata should be computed for checked transactions")
}
}
#[cfg(feature = "test-helpers")]
impl<Tx: IntoChecked + Default> Default for Checked<Tx>
where
Checked<Tx>: CheckPredicates,
{
fn default() -> Self {
Tx::default()
.into_checked(Default::default(), &Default::default(), &Default::default())
.expect("default tx should produce a valid fully checked transaction")
}
}
impl<Tx: IntoChecked> From<Checked<Tx>> for (Tx, Tx::Metadata) {
fn from(checked: Checked<Tx>) -> Self {
let Checked {
transaction,
metadata,
..
} = checked;
(transaction, metadata)
}
}
impl<Tx: IntoChecked> AsRef<Tx> for Checked<Tx> {
fn as_ref(&self) -> &Tx {
&self.transaction
}
}
#[cfg(feature = "test-helpers")]
impl<Tx: IntoChecked> AsMut<Tx> for Checked<Tx> {
fn as_mut(&mut self) -> &mut Tx {
&mut self.transaction
}
}
impl<Tx: IntoChecked> Borrow<Tx> for Checked<Tx> {
fn borrow(&self) -> &Tx {
self.transaction()
}
}
pub trait IntoChecked: FormatValidityChecks + Sized {
type Metadata: Sized;
fn into_checked(
self,
block_height: BlockHeight,
params: &ConsensusParameters,
gas_costs: &GasCosts,
) -> Result<Checked<Self>, CheckError>
where
Checked<Self>: CheckPredicates,
{
self.into_checked_basic(block_height, params)?
.check_signatures(¶ms.chain_id)?
.check_predicates(params, gas_costs)
}
fn into_checked_basic(
self,
block_height: BlockHeight,
params: &ConsensusParameters,
) -> Result<Checked<Self>, CheckError>;
}
#[async_trait::async_trait]
pub trait CheckPredicates: Sized {
fn check_predicates(
self,
params: &ConsensusParameters,
gas_costs: &GasCosts,
) -> Result<Self, CheckError>;
async fn check_predicates_async<E: ParallelExecutor>(
self,
params: &ConsensusParameters,
gas_costs: &GasCosts,
) -> Result<Self, CheckError>;
}
#[async_trait::async_trait]
pub trait EstimatePredicates: Sized {
fn estimate_predicates(
&mut self,
params: &ConsensusParameters,
gas_costs: &GasCosts,
) -> Result<(), CheckError>;
async fn estimate_predicates_async<E: ParallelExecutor>(
&mut self,
params: &ConsensusParameters,
gas_costs: &GasCosts,
) -> Result<(), CheckError>;
}
#[async_trait::async_trait]
pub trait ParallelExecutor {
type Task: Future + Send + 'static;
fn create_task<F>(func: F) -> Self::Task
where
F: FnOnce() -> Result<(Word, usize), PredicateVerificationFailed>
+ Send
+ 'static;
async fn execute_tasks(
futures: Vec<Self::Task>,
) -> Vec<Result<(Word, usize), PredicateVerificationFailed>>;
}
#[async_trait::async_trait]
impl<Tx> CheckPredicates for Checked<Tx>
where
Tx: ExecutableTransaction + Send + Sync + 'static,
<Tx as IntoChecked>::Metadata: crate::interpreter::CheckedMetadata + Send + Sync,
{
fn check_predicates(
mut self,
params: &ConsensusParameters,
gas_costs: &GasCosts,
) -> Result<Self, CheckError> {
if !self.checks_bitmask.contains(Checks::Predicates) {
let checked = Interpreter::<PredicateStorage>::check_predicates(
&self,
*params,
gas_costs.clone(),
)?;
self.checks_bitmask.insert(Checks::Predicates);
self.metadata.set_gas_used_by_predicates(checked.gas_used());
}
Ok(self)
}
async fn check_predicates_async<E>(
mut self,
params: &ConsensusParameters,
gas_costs: &GasCosts,
) -> Result<Self, CheckError>
where
E: ParallelExecutor,
{
if !self.checks_bitmask.contains(Checks::Predicates) {
let predicates_checked =
Interpreter::<PredicateStorage>::check_predicates_async::<_, E>(
&self,
*params,
gas_costs.clone(),
)
.await?;
self.checks_bitmask.insert(Checks::Predicates);
self.metadata
.set_gas_used_by_predicates(predicates_checked.gas_used());
Ok(self)
} else {
Ok(self)
}
}
}
#[async_trait::async_trait]
impl<Tx: ExecutableTransaction + Send + Sync + 'static> EstimatePredicates for Tx {
fn estimate_predicates(
&mut self,
params: &ConsensusParameters,
gas_costs: &GasCosts,
) -> Result<(), CheckError> {
let AvailableBalances {
non_retryable_balances,
retryable_balance,
..
} = initial_free_balances(self, params)?;
let balances: InitialBalances = InitialBalances {
non_retryable: NonRetryableFreeBalances(non_retryable_balances),
retryable: Some(RetryableAmount(retryable_balance)),
};
Interpreter::<PredicateStorage>::estimate_predicates(
self,
balances,
*params,
gas_costs.clone(),
)?;
Ok(())
}
async fn estimate_predicates_async<E>(
&mut self,
params: &ConsensusParameters,
gas_costs: &GasCosts,
) -> Result<(), CheckError>
where
E: ParallelExecutor,
{
let AvailableBalances {
non_retryable_balances,
retryable_balance,
..
} = initial_free_balances(self, params)?;
let balances: InitialBalances = InitialBalances {
non_retryable: NonRetryableFreeBalances(non_retryable_balances),
retryable: Some(RetryableAmount(retryable_balance)),
};
Interpreter::<PredicateStorage>::estimate_predicates_async::<_, E>(
self,
balances,
*params,
gas_costs.clone(),
)
.await?;
Ok(())
}
}
#[async_trait::async_trait]
impl EstimatePredicates for Transaction {
fn estimate_predicates(
&mut self,
params: &ConsensusParameters,
gas_costs: &GasCosts,
) -> Result<(), CheckError> {
match self {
Transaction::Script(script) => script.estimate_predicates(params, gas_costs),
Transaction::Create(create) => create.estimate_predicates(params, gas_costs),
Transaction::Mint(_) => Ok(()),
}
}
async fn estimate_predicates_async<E: ParallelExecutor>(
&mut self,
params: &ConsensusParameters,
gas_costs: &GasCosts,
) -> Result<(), CheckError> {
match self {
Transaction::Script(script) => {
script
.estimate_predicates_async::<E>(params, gas_costs)
.await
}
Transaction::Create(create) => {
create
.estimate_predicates_async::<E>(params, gas_costs)
.await
}
Transaction::Mint(_) => Ok(()),
}
}
}
#[async_trait::async_trait]
impl CheckPredicates for Checked<Mint> {
fn check_predicates(
mut self,
_params: &ConsensusParameters,
_gas_costs: &GasCosts,
) -> Result<Self, CheckError> {
self.checks_bitmask.insert(Checks::Predicates);
Ok(self)
}
async fn check_predicates_async<E: ParallelExecutor>(
mut self,
_params: &ConsensusParameters,
_gas_costs: &GasCosts,
) -> Result<Self, CheckError> {
self.checks_bitmask.insert(Checks::Predicates);
Ok(self)
}
}
#[async_trait::async_trait]
impl CheckPredicates for Checked<Transaction> {
fn check_predicates(
self,
params: &ConsensusParameters,
gas_costs: &GasCosts,
) -> Result<Self, CheckError> {
let checked_transaction: CheckedTransaction = self.into();
let checked_transaction: CheckedTransaction = match checked_transaction {
CheckedTransaction::Script(tx) => {
CheckPredicates::check_predicates(tx, params, gas_costs)?.into()
}
CheckedTransaction::Create(tx) => {
CheckPredicates::check_predicates(tx, params, gas_costs)?.into()
}
CheckedTransaction::Mint(tx) => {
CheckPredicates::check_predicates(tx, params, gas_costs)?.into()
}
};
Ok(checked_transaction.into())
}
async fn check_predicates_async<E>(
self,
params: &ConsensusParameters,
gas_costs: &GasCosts,
) -> Result<Self, CheckError>
where
E: ParallelExecutor,
{
let checked_transaction: CheckedTransaction = self.into();
let checked_transaction: CheckedTransaction = match checked_transaction {
CheckedTransaction::Script(tx) => {
CheckPredicates::check_predicates_async::<E>(tx, params, gas_costs)
.await?
.into()
}
CheckedTransaction::Create(tx) => {
CheckPredicates::check_predicates_async::<E>(tx, params, gas_costs)
.await?
.into()
}
CheckedTransaction::Mint(tx) => {
CheckPredicates::check_predicates_async::<E>(tx, params, gas_costs)
.await?
.into()
}
};
Ok(checked_transaction.into())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[allow(missing_docs)]
pub enum CheckedTransaction {
Script(Checked<Script>),
Create(Checked<Create>),
Mint(Checked<Mint>),
}
impl From<Checked<Transaction>> for CheckedTransaction {
fn from(checked: Checked<Transaction>) -> Self {
let Checked {
transaction,
metadata,
checks_bitmask,
} = checked;
match (transaction, metadata) {
(Transaction::Script(transaction), CheckedMetadata::Script(metadata)) => {
Self::Script(Checked::new(transaction, metadata, checks_bitmask))
}
(Transaction::Create(transaction), CheckedMetadata::Create(metadata)) => {
Self::Create(Checked::new(transaction, metadata, checks_bitmask))
}
(Transaction::Mint(transaction), CheckedMetadata::Mint(metadata)) => {
Self::Mint(Checked::new(transaction, metadata, checks_bitmask))
}
(Transaction::Script(_), _) => unreachable!(),
(Transaction::Create(_), _) => unreachable!(),
(Transaction::Mint(_), _) => unreachable!(),
}
}
}
impl From<Checked<Script>> for CheckedTransaction {
fn from(checked: Checked<Script>) -> Self {
Self::Script(checked)
}
}
impl From<Checked<Create>> for CheckedTransaction {
fn from(checked: Checked<Create>) -> Self {
Self::Create(checked)
}
}
impl From<Checked<Mint>> for CheckedTransaction {
fn from(checked: Checked<Mint>) -> Self {
Self::Mint(checked)
}
}
impl From<CheckedTransaction> for Checked<Transaction> {
fn from(checked: CheckedTransaction) -> Self {
match checked {
CheckedTransaction::Script(Checked {
transaction,
metadata,
checks_bitmask,
}) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
CheckedTransaction::Create(Checked {
transaction,
metadata,
checks_bitmask,
}) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
CheckedTransaction::Mint(Checked {
transaction,
metadata,
checks_bitmask,
}) => Checked::new(transaction.into(), metadata.into(), checks_bitmask),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[allow(missing_docs)]
pub enum CheckedMetadata {
Script(<Script as IntoChecked>::Metadata),
Create(<Create as IntoChecked>::Metadata),
Mint(<Mint as IntoChecked>::Metadata),
}
impl From<<Script as IntoChecked>::Metadata> for CheckedMetadata {
fn from(metadata: <Script as IntoChecked>::Metadata) -> Self {
Self::Script(metadata)
}
}
impl From<<Create as IntoChecked>::Metadata> for CheckedMetadata {
fn from(metadata: <Create as IntoChecked>::Metadata) -> Self {
Self::Create(metadata)
}
}
impl From<<Mint as IntoChecked>::Metadata> for CheckedMetadata {
fn from(metadata: <Mint as IntoChecked>::Metadata) -> Self {
Self::Mint(metadata)
}
}
impl IntoChecked for Transaction {
type Metadata = CheckedMetadata;
fn into_checked_basic(
self,
block_height: BlockHeight,
params: &ConsensusParameters,
) -> Result<Checked<Self>, CheckError> {
let (transaction, metadata) = match self {
Transaction::Script(script) => {
let (transaction, metadata) =
script.into_checked_basic(block_height, params)?.into();
(transaction.into(), metadata.into())
}
Transaction::Create(create) => {
let (transaction, metadata) =
create.into_checked_basic(block_height, params)?.into();
(transaction.into(), metadata.into())
}
Transaction::Mint(mint) => {
let (transaction, metadata) =
mint.into_checked_basic(block_height, params)?.into();
(transaction.into(), metadata.into())
}
};
Ok(Checked::basic(transaction, metadata))
}
}
#[cfg(test)]
mod tests {
use super::*;
use fuel_asm::op;
use fuel_crypto::SecretKey;
use fuel_tx::{
CheckError,
Script,
TransactionBuilder,
};
use quickcheck::TestResult;
use quickcheck_macros::quickcheck;
use rand::{
rngs::StdRng,
Rng,
SeedableRng,
};
#[test]
fn checked_tx_accepts_valid_tx() {
let rng = &mut StdRng::seed_from_u64(2322u64);
let gas_price = 10;
let gas_limit = 1000;
let input_amount = 1000;
let output_amount = 10;
let tx = valid_coin_tx(rng, gas_price, gas_limit, input_amount, output_amount);
let checked = tx
.clone()
.into_checked(
Default::default(),
&ConsensusParameters::DEFAULT,
&Default::default(),
)
.expect("Expected valid transaction");
assert_eq!(checked.transaction(), &tx);
assert_eq!(
checked.metadata().non_retryable_balances[&AssetId::default()],
input_amount - checked.metadata().fee.max_fee() - output_amount
);
}
#[test]
fn checked_tx_accepts_valid_signed_message_input_fees() {
let rng = &mut StdRng::seed_from_u64(2322u64);
let input_amount = 100;
let gas_price = 100;
let gas_limit = 1000;
let tx = signed_message_coin_tx(rng, gas_price, gas_limit, input_amount);
let checked = tx
.into_checked(
Default::default(),
&ConsensusParameters::DEFAULT,
&Default::default(),
)
.expect("Expected valid transaction");
assert_eq!(
checked.metadata().non_retryable_balances[&AssetId::default()],
input_amount - checked.metadata().fee.max_fee()
);
}
#[test]
fn checked_tx_excludes_message_output_amount_from_fee() {
let rng = &mut StdRng::seed_from_u64(2322u64);
let input_amount = 100;
let gas_price = 100;
let gas_limit = 1000;
let tx = signed_message_coin_tx(rng, gas_price, gas_limit, input_amount);
let checked = tx
.into_checked(
Default::default(),
&ConsensusParameters::DEFAULT,
&Default::default(),
)
.expect("Expected valid transaction");
assert_eq!(
checked.metadata().non_retryable_balances[&AssetId::default()],
input_amount - checked.metadata().fee.max_fee()
);
}
#[test]
fn message_data_signed_message_is_not_used_to_cover_fees() {
let rng = &mut StdRng::seed_from_u64(2322u64);
let input_amount = 100;
let gas_price = 100;
let gas_limit = 1000;
let tx = TransactionBuilder::script(vec![], vec![])
.gas_price(gas_price)
.gas_limit(gas_limit)
.add_unsigned_message_input(rng.gen(), rng.gen(), rng.gen(), input_amount, vec![0xff; 10])
.add_unsigned_coin_input(rng.gen(), rng.gen(), 0, AssetId::BASE, rng.gen(), rng.gen())
.finalize();
let err = tx
.into_checked(
Default::default(),
&ConsensusParameters::DEFAULT,
&Default::default(),
)
.expect_err("Expected valid transaction");
assert!(matches!(
err,
CheckError::InsufficientFeeAmount {
expected: _,
provided: 0
}
));
}
#[test]
fn message_data_predicate_message_is_not_used_to_cover_fees() {
let rng = &mut StdRng::seed_from_u64(2322u64);
let input_amount = 100;
let gas_price = 100;
let gas_limit = 1000;
let tx = TransactionBuilder::script(vec![], vec![])
.gas_price(gas_price)
.gas_limit(gas_limit)
.add_input(Input::message_data_predicate(
rng.gen(),
rng.gen(),
input_amount,
rng.gen(),
Default::default(),
vec![0xff; 10],
vec![0xaa; 10],
vec![0xbb; 10],
))
.add_unsigned_coin_input(rng.gen(), rng.gen(), 0, AssetId::BASE, rng.gen(), rng.gen())
.finalize();
let err = tx
.into_checked(
Default::default(),
&ConsensusParameters::DEFAULT,
&Default::default(),
)
.expect_err("Expected valid transaction");
assert!(matches!(
err,
CheckError::InsufficientFeeAmount {
expected: _,
provided: 0
}
));
}
#[quickcheck]
fn max_fee_coin_input(
gas_price: u64,
gas_limit: u64,
input_amount: u64,
gas_price_factor: u64,
seed: u64,
) -> TestResult {
if gas_price_factor == 0 {
return TestResult::discard()
}
let rng = &mut StdRng::seed_from_u64(seed);
let params = ConsensusParameters::DEFAULT.with_gas_price_factor(gas_price_factor);
let predicate_gas_used = rng.gen();
let tx =
predicate_tx(rng, gas_price, gas_limit, input_amount, predicate_gas_used);
if let Ok(valid) = is_valid_max_fee(&tx, ¶ms) {
TestResult::from_bool(valid)
} else {
TestResult::discard()
}
}
#[quickcheck]
fn min_fee_coin_input(
gas_price: u64,
gas_limit: u64,
input_amount: u64,
gas_price_factor: u64,
seed: u64,
) -> TestResult {
if gas_price_factor == 0 {
return TestResult::discard()
}
let rng = &mut StdRng::seed_from_u64(seed);
let params = ConsensusParameters::DEFAULT.with_gas_price_factor(gas_price_factor);
let predicate_gas_used = rng.gen();
let tx =
predicate_tx(rng, gas_price, gas_limit, input_amount, predicate_gas_used);
if let Ok(valid) = is_valid_max_fee(&tx, ¶ms) {
TestResult::from_bool(valid)
} else {
TestResult::discard()
}
}
#[quickcheck]
fn max_fee_message_input(
gas_price: u64,
gas_limit: u64,
input_amount: u64,
gas_price_factor: u64,
seed: u64,
) -> TestResult {
if gas_price_factor == 0 {
return TestResult::discard()
}
let rng = &mut StdRng::seed_from_u64(seed);
let params = ConsensusParameters::DEFAULT.with_gas_price_factor(gas_price_factor);
let tx = predicate_message_coin_tx(rng, gas_price, gas_limit, input_amount);
if let Ok(valid) = is_valid_max_fee(&tx, ¶ms) {
TestResult::from_bool(valid)
} else {
TestResult::discard()
}
}
#[quickcheck]
fn min_fee_message_input(
gas_price: u64,
gas_limit: u64,
input_amount: u64,
gas_price_factor: u64,
seed: u64,
) -> TestResult {
if gas_price_factor == 0 {
return TestResult::discard()
}
let rng = &mut StdRng::seed_from_u64(seed);
let params = ConsensusParameters::DEFAULT.with_gas_price_factor(gas_price_factor);
let tx = predicate_message_coin_tx(rng, gas_price, gas_limit, input_amount);
if let Ok(valid) = is_valid_min_fee(&tx, ¶ms) {
TestResult::from_bool(valid)
} else {
TestResult::discard()
}
}
#[test]
fn checked_tx_rejects_invalid_tx() {
let rng = &mut StdRng::seed_from_u64(2322u64);
let asset = rng.gen();
let gas_price = 1;
let gas_limit = 100;
let input_amount = 1_000;
let tx = TransactionBuilder::script(vec![], vec![])
.gas_price(gas_price)
.gas_limit(gas_limit)
.add_input(Input::coin_signed(
rng.gen(),
rng.gen(),
input_amount,
asset,
rng.gen(),
Default::default(),
Default::default(),
))
.add_input(Input::contract(
rng.gen(),
rng.gen(),
rng.gen(),
rng.gen(),
rng.gen(),
))
.add_output(Output::contract(1, rng.gen(), rng.gen()))
.add_output(Output::coin(rng.gen(), 10, asset))
.add_output(Output::change(rng.gen(), 0, asset))
.add_witness(Default::default())
.finalize();
let checked = tx
.into_checked(
Default::default(),
&ConsensusParameters::DEFAULT,
&Default::default(),
)
.expect_err("Expected invalid transaction");
assert_eq!(
CheckError::InsufficientFeeAmount {
expected: 1,
provided: 0
},
checked
);
}
#[test]
fn checked_tx_fails_when_provided_fees_dont_cover_byte_costs() {
let rng = &mut StdRng::seed_from_u64(2322u64);
let input_amount = 1;
let gas_price = 2; let gas_limit = 0; let factor = 1;
let params = ConsensusParameters::default().with_gas_price_factor(factor);
let transaction = base_asset_tx(rng, input_amount, gas_price, gas_limit);
let err = transaction
.into_checked(Default::default(), ¶ms, &Default::default())
.expect_err("insufficient fee amount expected");
let provided = match err {
CheckError::InsufficientFeeAmount { provided, .. } => provided,
_ => panic!("expected insufficient fee amount; found {err:?}"),
};
assert_eq!(provided, input_amount);
}
#[test]
fn checked_tx_fails_when_provided_fees_dont_cover_gas_costs() {
let rng = &mut StdRng::seed_from_u64(2322u64);
let input_amount = 10;
let factor = 1;
let params = ConsensusParameters::default().with_gas_price_factor(factor);
let gas_price = 1;
let gas_limit = input_amount + 1; let transaction = base_asset_tx(rng, input_amount, gas_price, gas_limit);
let err = transaction
.into_checked(Default::default(), ¶ms, &Default::default())
.expect_err("insufficient fee amount expected");
let provided = match err {
CheckError::InsufficientFeeAmount { provided, .. } => provided,
_ => panic!("expected insufficient fee amount; found {err:?}"),
};
assert_eq!(provided, input_amount);
}
#[test]
fn bytes_fee_cant_overflow() {
let rng = &mut StdRng::seed_from_u64(2322u64);
let input_amount = 1000;
let gas_price = Word::MAX;
let gas_limit = 0; let params = ConsensusParameters::default().with_gas_price_factor(1);
let transaction = base_asset_tx(rng, input_amount, gas_price, gas_limit);
let err = transaction
.into_checked(Default::default(), ¶ms, &Default::default())
.expect_err("overflow expected");
assert_eq!(err, CheckError::ArithmeticOverflow);
}
#[test]
fn gas_fee_cant_overflow() {
let rng = &mut StdRng::seed_from_u64(2322u64);
let input_amount = 1000;
let gas_price = Word::MAX;
let gas_limit = 2; let params = ConsensusParameters::default().with_gas_price_factor(1);
let transaction = base_asset_tx(rng, input_amount, gas_price, gas_limit);
let err = transaction
.into_checked(Default::default(), ¶ms, &Default::default())
.expect_err("overflow expected");
assert_eq!(err, CheckError::ArithmeticOverflow);
}
#[test]
fn checked_tx_fails_if_asset_is_overspent_by_coin_output() {
let input_amount = 1_000;
let rng = &mut StdRng::seed_from_u64(2322u64);
let secret = SecretKey::random(rng);
let any_asset = rng.gen();
let tx = TransactionBuilder::script(vec![], vec![])
.gas_price(1)
.gas_limit(100)
.add_unsigned_coin_input(
secret,
rng.gen(),
input_amount,
AssetId::default(),
rng.gen(),
Default::default(),
)
.add_output(Output::change(rng.gen(), 0, AssetId::default()))
.add_unsigned_coin_input(
secret,
rng.gen(),
input_amount,
any_asset,
rng.gen(),
Default::default(),
)
.add_output(Output::coin(rng.gen(), input_amount + 1, any_asset))
.add_output(Output::change(rng.gen(), 0, any_asset))
.finalize();
let checked = tx
.into_checked(
Default::default(),
&ConsensusParameters::DEFAULT,
&Default::default(),
)
.expect_err("Expected valid transaction");
assert_eq!(
CheckError::InsufficientInputAmount {
asset: any_asset,
expected: input_amount + 1,
provided: input_amount
},
checked
);
}
#[test]
fn basic_check_marks_basic_flag() {
let block_height = 1.into();
let params = ConsensusParameters::default();
let tx = Transaction::default_test_tx();
let checked = tx.into_checked_basic(block_height, ¶ms).unwrap();
assert!(checked.checks().contains(Checks::Basic));
}
#[test]
fn signatures_check_marks_signatures_flag() {
let mut rng = StdRng::seed_from_u64(1);
let block_height = 1.into();
let params = ConsensusParameters::default();
let tx = valid_coin_tx(&mut rng, 1, 100000, 1000000, 10);
let checked = tx
.into_checked_basic(block_height, ¶ms)
.unwrap()
.check_signatures(¶ms.chain_id)
.unwrap();
assert!(checked
.checks()
.contains(Checks::Basic | Checks::Signatures));
}
#[test]
fn predicates_check_marks_predicate_flag() {
let mut rng = StdRng::seed_from_u64(1);
let block_height = 1.into();
let params = ConsensusParameters::default();
let gas_costs = GasCosts::free();
let tx = predicate_tx(&mut rng, 1, 1000000, 1000000, 0);
let checked = tx
.into_checked_basic(block_height, ¶ms)
.unwrap()
.check_predicates(¶ms, &gas_costs)
.unwrap();
assert!(checked
.checks()
.contains(Checks::Basic | Checks::Predicates));
}
fn is_valid_max_fee<Tx>(
tx: &Tx,
params: &ConsensusParameters,
) -> Result<bool, CheckError>
where
Tx: Chargeable + field::Inputs + field::Outputs,
{
let available_balances = balances::initial_free_balances(tx, params)?;
let bytes = (tx.metered_bytes_size() as u128)
* params.gas_per_byte as u128
* tx.price() as u128;
let gas = tx.limit() as u128 * tx.price() as u128;
let total = bytes + gas;
let fee = total / params.gas_price_factor as u128;
let fee_remainder =
(total.rem_euclid(params.gas_price_factor as u128) > 0) as u128;
let rounded_fee = (fee + fee_remainder) as u64;
Ok(rounded_fee == available_balances.fee.max_fee())
}
fn is_valid_min_fee<Tx>(
tx: &Tx,
params: &ConsensusParameters,
) -> Result<bool, CheckError>
where
Tx: Chargeable + field::Inputs + field::Outputs,
{
let available_balances = balances::initial_free_balances(tx, params)?;
let bytes = (tx.metered_bytes_size() as u128
+ tx.gas_used_by_predicates() as u128)
* params.gas_per_byte as u128
* tx.price() as u128;
let fee = bytes / params.gas_price_factor as u128;
let fee_remainder =
(bytes.rem_euclid(params.gas_price_factor as u128) > 0) as u128;
let rounded_fee = (fee + fee_remainder) as u64;
Ok(rounded_fee == available_balances.fee.min_fee())
}
fn valid_coin_tx(
rng: &mut StdRng,
gas_price: u64,
gas_limit: u64,
input_amount: u64,
output_amount: u64,
) -> Script {
let asset = AssetId::default();
TransactionBuilder::script(vec![], vec![])
.gas_price(gas_price)
.gas_limit(gas_limit)
.add_unsigned_coin_input(
rng.gen(),
rng.gen(),
input_amount,
asset,
rng.gen(),
Default::default(),
)
.add_input(Input::contract(
rng.gen(),
rng.gen(),
rng.gen(),
rng.gen(),
rng.gen(),
))
.add_output(Output::contract(1, rng.gen(), rng.gen()))
.add_output(Output::coin(rng.gen(), output_amount, asset))
.add_output(Output::change(rng.gen(), 0, asset))
.finalize()
}
fn predicate_tx(
rng: &mut StdRng,
gas_price: u64,
gas_limit: u64,
fee_input_amount: u64,
predicate_gas_used: u64,
) -> Script {
let asset = AssetId::default();
let predicate = vec![op::ret(1)].into_iter().collect::<Vec<u8>>();
let owner =
Input::predicate_owner(&predicate, &ConsensusParameters::DEFAULT.chain_id);
TransactionBuilder::script(vec![], vec![])
.gas_price(gas_price)
.gas_limit(gas_limit)
.add_input(Input::coin_predicate(
rng.gen(),
owner,
fee_input_amount,
asset,
rng.gen(),
Default::default(),
predicate_gas_used,
predicate,
vec![],
))
.add_output(Output::change(rng.gen(), 0, asset))
.finalize()
}
fn signed_message_coin_tx(
rng: &mut StdRng,
gas_price: u64,
gas_limit: u64,
input_amount: u64,
) -> Script {
TransactionBuilder::script(vec![], vec![])
.gas_price(gas_price)
.gas_limit(gas_limit)
.add_unsigned_message_input(
rng.gen(),
rng.gen(),
rng.gen(),
input_amount,
vec![],
)
.finalize()
}
fn predicate_message_coin_tx(
rng: &mut StdRng,
gas_price: u64,
gas_limit: u64,
input_amount: u64,
) -> Script {
TransactionBuilder::script(vec![], vec![])
.gas_price(gas_price)
.gas_limit(gas_limit)
.add_input(Input::message_coin_predicate(
rng.gen(),
rng.gen(),
input_amount,
rng.gen(),
Default::default(),
vec![],
vec![],
))
.finalize()
}
fn base_asset_tx(
rng: &mut StdRng,
input_amount: u64,
gas_price: u64,
gas_limit: u64,
) -> Script {
TransactionBuilder::script(vec![], vec![])
.gas_price(gas_price)
.gas_limit(gas_limit)
.add_unsigned_coin_input(
rng.gen(),
rng.gen(),
input_amount,
AssetId::default(),
rng.gen(),
Default::default(),
)
.add_output(Output::change(rng.gen(), 0, AssetId::default()))
.finalize()
}
}