use crate::{
checked_transaction::{
CheckPredicateParams,
EstimatePredicates,
},
prelude::{
MemoryInstance,
*,
},
};
use core::str::FromStr;
use fuel_asm::{
RegId,
op,
};
use fuel_tx::{
ConsensusParameters,
Input,
TransactionBuilder,
UtxoId,
field::{
Inputs,
Outputs,
ReceiptsRoot,
StorageSlots,
},
input::coin::CoinPredicate,
};
use fuel_types::BlockHeight;
use rand::{
Rng,
SeedableRng,
rngs::StdRng,
};
use crate::storage::predicate::EmptyStorage;
#[cfg(feature = "alloc")]
use alloc::vec;
#[test]
fn transaction_can_be_executed_after_maturity() {
const MATURITY: BlockHeight = BlockHeight::new(1);
const BLOCK_HEIGHT: BlockHeight = BlockHeight::new(2);
let arb_max_fee = 1;
let rng = &mut StdRng::seed_from_u64(2322u64);
let tx = TransactionBuilder::script(
Some(op::ret(1)).into_iter().collect(),
Default::default(),
)
.max_fee_limit(arb_max_fee)
.add_unsigned_coin_input(
SecretKey::random(rng),
rng.r#gen(),
arb_max_fee,
Default::default(),
rng.r#gen(),
)
.script_gas_limit(100)
.maturity(MATURITY)
.finalize_checked(BLOCK_HEIGHT);
let result = TestBuilder::new(2322u64)
.block_height(BLOCK_HEIGHT)
.execute_tx(tx);
assert!(result.is_ok());
}
#[test]
fn transaction__execution__works_before_expiration() {
let arb_max_fee = 1;
let rng = &mut StdRng::seed_from_u64(2322u64);
const EXPIRATION: BlockHeight = BlockHeight::new(2);
const BLOCK_HEIGHT: BlockHeight = BlockHeight::new(1);
let tx = TransactionBuilder::script(
Some(op::ret(1)).into_iter().collect(),
Default::default(),
)
.max_fee_limit(arb_max_fee)
.add_unsigned_coin_input(
SecretKey::random(rng),
rng.r#gen(),
arb_max_fee,
Default::default(),
rng.r#gen(),
)
.script_gas_limit(100)
.expiration(EXPIRATION)
.finalize_checked(BLOCK_HEIGHT);
let result = TestBuilder::new(2322u64)
.block_height(BLOCK_HEIGHT)
.execute_tx(tx);
assert!(result.is_ok());
}
#[test]
fn transaction__execution__works_current_height_expiration() {
let arb_max_fee = 1;
let rng = &mut StdRng::seed_from_u64(2322u64);
const EXPIRATION: BlockHeight = BlockHeight::new(1);
const BLOCK_HEIGHT: BlockHeight = BlockHeight::new(1);
let tx = TransactionBuilder::script(
Some(op::ret(1)).into_iter().collect(),
Default::default(),
)
.max_fee_limit(arb_max_fee)
.add_unsigned_coin_input(
SecretKey::random(rng),
rng.r#gen(),
arb_max_fee,
Default::default(),
rng.r#gen(),
)
.script_gas_limit(100)
.expiration(EXPIRATION)
.finalize_checked(BLOCK_HEIGHT);
let result = TestBuilder::new(2322u64)
.block_height(BLOCK_HEIGHT)
.execute_tx(tx);
assert!(result.is_ok());
}
#[allow(deprecated)]
#[test]
fn malleable_fields_do_not_affect_validity_of_create() {
let params = ConsensusParameters::default();
let tx_start_ptr = params.tx_params().tx_offset();
let tx_size_ptr = tx_start_ptr - 8;
let witness_count_offset =
<Create as StorageSlots>::storage_slots_offset_static() - 8;
let predicate_bytecode = [
op::movi(0x21, tx_size_ptr as u32),
op::lw(0x21, 0x21, 0),
op::addi(0x22, 0x21, 8 + 32),
op::aloc(0x22),
op::addi(0x22, RegId::HP, 32), op::gtf_args(0x20, 0x00, GTFArgs::InputCoinPredicateData),
op::mcpi(0x22, 0x20, 8), op::movi(0x20, tx_start_ptr as u32),
op::addi(0x23, 0x22, 8), op::mcp(0x23, 0x20, 0x21), op::gtf_args(0x26, 0x00, GTFArgs::ScriptWitnessesCount),
op::eq(0x26, 0x26, RegId::ONE),
op::jnzf(0x26, 0x00, 1),
op::ret(0),
op::addi(0x26, 0x23, witness_count_offset as u16), op::sw(0x26, RegId::ZERO, 0), op::s256(RegId::HP, 0x22, 0x21), op::movi(0x25, 32), op::meq(0x26, RegId::HP, 0x0, 0x25),
op::ret(0x26),
]
.into_iter()
.collect();
let predicate_data = params.chain_id().to_be_bytes().to_vec();
let predicate_owner = Input::predicate_owner(&predicate_bytecode);
let mut tx = TransactionBuilder::create(vec![].into(), Salt::zeroed(), vec![])
.add_input(Input::coin_predicate(
UtxoId::new([4; 32].into(), 0),
predicate_owner,
123456789,
AssetId::default(),
Default::default(),
0,
predicate_bytecode,
predicate_data,
))
.add_contract_created()
.add_output(Output::change(
Default::default(),
Default::default(),
Default::default(),
))
.finalize();
tx.estimate_predicates(
&CheckPredicateParams::from(¶ms),
MemoryInstance::new(),
&EmptyStorage,
)
.expect("Should estimate predicate");
let run_tx = |tx: Create| tx.into_checked(0u32.into(), ¶ms).map(|_| ());
let result = run_tx(tx.clone());
assert_eq!(result, Ok(()));
{
let mut tx = tx.clone();
match tx.inputs_mut()[0] {
Input::CoinPredicate(CoinPredicate {
ref mut tx_pointer, ..
}) => {
#[cfg(not(feature = "u32-tx-pointer"))]
{
*tx_pointer = TxPointer::from_str("123456780001").unwrap()
}
#[cfg(feature = "u32-tx-pointer")]
{
*tx_pointer = TxPointer::from_str("1234567800000001").unwrap()
}
}
_ => unreachable!(),
};
match tx.outputs_mut()[1] {
Output::Change { ref mut amount, .. } => {
*amount = 123456789;
}
_ => unreachable!(),
};
let result = run_tx(tx);
assert_eq!(result, Ok(()));
}
}
#[test]
fn malleable_fields_do_not_affect_validity_of_script() {
let params = ConsensusParameters::default();
let tx_start_ptr = params.tx_params().tx_offset();
let tx_size_ptr = tx_start_ptr - 8;
let predicate_bytecode = [
op::movi(0x21, tx_size_ptr as u32),
op::lw(0x21, 0x21, 0),
op::addi(0x22, 0x21, 8 + 32),
op::aloc(0x22),
op::addi(0x22, RegId::HP, 32), op::gtf_args(0x20, 0x00, GTFArgs::InputCoinPredicateData),
op::mcpi(0x22, 0x20, 8), op::movi(0x20, tx_start_ptr as u32),
op::addi(0x23, 0x22, 8), op::mcp(0x23, 0x20, 0x21), op::addi(0x24, 0x21, 8), op::s256(RegId::HP, 0x22, 0x24), op::movi(0x25, 32), op::meq(0x26, RegId::HP, 0x0, 0x25),
op::ret(0x26),
]
.into_iter()
.collect();
let predicate_data = params.chain_id().to_be_bytes().to_vec();
let predicate_owner = Input::predicate_owner(&predicate_bytecode);
let mut tx = TransactionBuilder::script(vec![], vec![])
.add_input(Input::coin_predicate(
UtxoId::new([4; 32].into(), 0),
predicate_owner,
123456789,
AssetId::default(),
Default::default(),
0,
predicate_bytecode,
predicate_data,
))
.add_input(Input::contract(
Default::default(),
Default::default(),
Default::default(),
Default::default(),
Contract::EMPTY_CONTRACT_ID,
))
.add_output(Output::variable(
Default::default(),
Default::default(),
Default::default(),
))
.add_output(Output::change(
Default::default(),
Default::default(),
Default::default(),
))
.add_output(Output::contract(1, Default::default(), Default::default()))
.script_gas_limit(1_000_000)
.finalize();
tx.estimate_predicates(
&CheckPredicateParams::from(¶ms),
MemoryInstance::new(),
&EmptyStorage,
)
.expect("Should estimate predicate");
let run_tx = |tx: Script| tx.into_checked(0u32.into(), ¶ms).map(|_| ());
let result = run_tx(tx.clone());
assert_eq!(result, Ok(()));
{
let mut tx = tx.clone();
*tx.receipts_root_mut() = [1u8; 32].into();
match tx.inputs_mut()[0] {
Input::CoinPredicate(CoinPredicate {
ref mut tx_pointer, ..
}) => {
#[cfg(not(feature = "u32-tx-pointer"))]
{
*tx_pointer = TxPointer::from_str("123456780001").unwrap()
}
#[cfg(feature = "u32-tx-pointer")]
{
*tx_pointer = TxPointer::from_str("1234567800000001").unwrap()
}
}
_ => unreachable!(),
};
match tx.inputs_mut()[1] {
Input::Contract(input::contract::Contract {
ref mut utxo_id,
ref mut balance_root,
ref mut state_root,
ref mut tx_pointer,
..
}) => {
*utxo_id = UtxoId::new([1; 32].into(), 0);
*balance_root = [2; 32].into();
*state_root = [3; 32].into();
#[cfg(not(feature = "u32-tx-pointer"))]
{
*tx_pointer = TxPointer::from_str("123456780001").unwrap();
}
#[cfg(feature = "u32-tx-pointer")]
{
*tx_pointer = TxPointer::from_str("1234567800000001").unwrap();
}
}
_ => unreachable!(),
};
match tx.outputs_mut()[0] {
Output::Variable {
ref mut to,
ref mut amount,
ref mut asset_id,
} => {
*to = [123; 32].into();
*amount = 123456789;
*asset_id = [213; 32].into();
}
_ => unreachable!(),
};
match tx.outputs_mut()[1] {
Output::Change { ref mut amount, .. } => {
*amount = 123456789;
}
_ => unreachable!(),
};
match tx.outputs_mut()[2] {
Output::Contract(output::contract::Contract {
ref mut balance_root,
ref mut state_root,
..
}) => {
*balance_root = [7; 32].into();
*state_root = [8; 32].into();
}
_ => unreachable!(),
};
let result = run_tx(tx);
assert_eq!(result, Ok(()));
}
}