use crate::{
prelude::*,
script_with_data_offset,
util::test_helpers::TestBuilder,
};
use fuel_asm::{
op,
RegId,
};
use fuel_tx::Witness;
use rand::{
rngs::StdRng,
Rng,
SeedableRng,
};
#[test]
fn prevent_contract_id_redeployment() {
let mut rng = StdRng::seed_from_u64(2322u64);
let mut client = MemoryClient::default();
let input_amount = 1000;
let gas_price = 0;
let spend_amount = 600;
let asset_id = AssetId::BASE;
#[rustfmt::skip]
let function_rvrt: Vec<Instruction> = vec![
op::rvrt(0),
];
let salt: Salt = rng.gen();
let program: Witness = function_rvrt.into_iter().collect::<Vec<u8>>().into();
let contract = Contract::from(program.as_ref());
let contract_root = contract.root();
let state_root = Contract::default_state_root();
let contract_undefined = contract.id(&salt, &contract_root, &state_root);
let output = Output::contract_created(contract_undefined, state_root);
let mut create = Transaction::create(
gas_price,
Default::default(),
Default::default(),
Default::default(),
salt,
vec![],
vec![],
vec![
output,
Output::change(rng.gen(), 0, asset_id),
Output::coin(rng.gen(), spend_amount, asset_id),
],
vec![program, Witness::default()],
);
create.add_unsigned_coin_input(
rng.gen(),
&Default::default(),
input_amount,
asset_id,
rng.gen(),
Default::default(),
1,
);
let create = create
.into_checked_basic(1.into(), &ConsensusParameters::default())
.expect("failed to generate checked tx");
client
.deploy(create.clone())
.expect("First create should be executed");
let mut txtor: Transactor<_, _> = client.into();
let result = txtor.deploy(create).unwrap_err();
assert_eq!(
result,
InterpreterError::Panic(PanicReason::ContractIdAlreadyDeployed)
);
}
#[test]
fn mint_burn() {
let mut test_context = TestBuilder::new(2322u64);
let mut balance = 1000;
let gas_limit = 1_000_000;
let program = vec![
op::addi(0x10, RegId::FP, CallFrame::a_offset() as Immediate12),
op::lw(0x10, 0x10, 0),
op::addi(0x11, RegId::FP, CallFrame::b_offset() as Immediate12),
op::lw(0x11, 0x11, 0),
op::movi(0x15, Bytes32::LEN as u32),
op::aloc(0x15),
op::jnei(0x10, RegId::ZERO, 9),
op::mint(0x11, RegId::HP),
op::ji(10),
op::burn(0x11, RegId::HP),
op::ret(RegId::ONE),
];
let contract_id = test_context.setup_contract(program, None, None).contract_id;
let asset_id = contract_id.asset_id(&Bytes32::zeroed());
let (script_call, _) = script_with_data_offset!(
data_offset,
vec![
op::movi(0x10, data_offset as Immediate18),
op::call(0x10, RegId::ZERO, 0x10, RegId::CGAS),
op::ret(RegId::ONE),
],
test_context.tx_offset()
);
let script_call_data = Call::new(contract_id, 0, balance).to_bytes();
let script_data_check_balance: Vec<u8> = asset_id
.as_ref()
.iter()
.chain(contract_id.as_ref().iter())
.copied()
.collect();
let (script_check_balance, _) = script_with_data_offset!(
data_offset,
vec![
op::movi(0x10, data_offset as Immediate18),
op::move_(0x11, 0x10),
op::addi(0x12, 0x10, AssetId::LEN as Immediate12),
op::bal(0x10, 0x11, 0x12),
op::log(0x10, RegId::ZERO, RegId::ZERO, RegId::ZERO),
op::ret(RegId::ONE),
],
test_context.tx_offset()
);
let result = test_context
.start_script(
script_check_balance.clone(),
script_data_check_balance.clone(),
)
.gas_limit(gas_limit)
.contract_input(contract_id)
.fee_input()
.contract_output(&contract_id)
.execute();
let storage_balance = result.receipts()[0].ra().expect("Balance expected");
assert_eq!(0, storage_balance);
test_context
.start_script(script_call.clone(), script_call_data)
.gas_limit(gas_limit)
.contract_input(contract_id)
.fee_input()
.contract_output(&contract_id)
.execute();
let result = test_context
.start_script(
script_check_balance.clone(),
script_data_check_balance.clone(),
)
.gas_limit(gas_limit)
.contract_input(contract_id)
.fee_input()
.contract_output(&contract_id)
.execute();
let storage_balance = result.receipts()[0].ra().expect("Balance expected");
assert_eq!(balance as Word, storage_balance);
let script_call_data = Call::new(contract_id, 1, balance + 1).to_bytes();
let result = test_context
.start_script(script_call.clone(), script_call_data)
.gas_limit(gas_limit)
.contract_input(contract_id)
.fee_input()
.contract_output(&contract_id)
.execute();
assert!(result.should_revert());
let result = test_context
.start_script(
script_check_balance.clone(),
script_data_check_balance.clone(),
)
.gas_limit(gas_limit)
.contract_input(contract_id)
.fee_input()
.contract_output(&contract_id)
.execute();
let storage_balance = result.receipts()[0].ra().expect("Balance expected");
assert_eq!(balance as Word, storage_balance);
let burn = 100;
let script_call_data = Call::new(contract_id, 1, burn).to_bytes();
test_context
.start_script(script_call.clone(), script_call_data)
.gas_limit(gas_limit)
.contract_input(contract_id)
.fee_input()
.contract_output(&contract_id)
.execute();
balance -= burn;
let result = test_context
.start_script(
script_check_balance.clone(),
script_data_check_balance.clone(),
)
.gas_limit(gas_limit)
.contract_input(contract_id)
.fee_input()
.contract_output(&contract_id)
.execute();
let storage_balance = result.receipts()[0].ra().expect("Balance expected");
assert_eq!(balance as Word, storage_balance);
let script_call_data = Call::new(contract_id, 1, balance).to_bytes();
test_context
.start_script(script_call, script_call_data)
.gas_limit(gas_limit)
.contract_input(contract_id)
.fee_input()
.contract_output(&contract_id)
.execute();
let result = test_context
.start_script(script_check_balance, script_data_check_balance)
.gas_limit(gas_limit)
.contract_input(contract_id)
.fee_input()
.contract_output(&contract_id)
.execute();
let storage_balance = result.receipts()[0].ra().expect("Balance expected");
assert_eq!(0, storage_balance);
}
#[test]
fn call_increases_contract_asset_balance_and_balance_register() {
let rng = &mut StdRng::seed_from_u64(2322u64);
let gas_limit = 1_000_000;
let asset_id: AssetId = rng.gen();
let call_amount = 500u64;
let mut test_context = TestBuilder::new(2322u64);
let contract_id = test_context
.setup_contract(vec![op::ret(RegId::BAL)], None, None)
.contract_id;
let (script_ops, offset) = script_with_data_offset!(
data_offset,
vec![
op::movi(0x10, data_offset + 32),
op::movi(0x11, call_amount as Immediate18),
op::movi(0x12, data_offset),
op::call(0x10, 0x11, 0x12, RegId::CGAS),
op::ret(RegId::ONE),
],
test_context.tx_offset()
);
let script_data: Vec<u8> = [
asset_id.as_ref(),
Call::new(contract_id, 0, offset as Word)
.to_bytes()
.as_slice(),
]
.into_iter()
.flatten()
.copied()
.collect();
let start_balance = test_context.get_contract_balance(&contract_id, &asset_id);
assert_eq!(start_balance, 0);
let transfer_tx = test_context
.start_script(script_ops, script_data)
.gas_limit(gas_limit)
.gas_price(0)
.coin_input(asset_id, call_amount)
.contract_input(contract_id)
.contract_output(&contract_id)
.change_output(asset_id)
.execute();
assert!(!transfer_tx.should_revert());
let end_balance = test_context.get_contract_balance(&contract_id, &asset_id);
assert_eq!(end_balance, call_amount);
assert_eq!(transfer_tx.receipts()[1].val().unwrap(), call_amount);
}
#[test]
fn call_decreases_internal_balance_and_increases_destination_contract_balance() {
let rng = &mut StdRng::seed_from_u64(2322u64);
let gas_limit = 1_000_000;
let asset_id: AssetId = rng.gen();
let call_amount = 500;
let initial_internal_balance = 1_000_000;
let mut test_context = TestBuilder::new(2322u64);
let dest_contract_id = test_context
.setup_contract(
vec![
op::ret(RegId::BAL),
],
None,
None,
)
.contract_id;
let program = vec![
op::addi(0x10, RegId::FP, CallFrame::a_offset() as Immediate12),
op::lw(0x10, 0x10, 0),
op::addi(0x11, RegId::FP, CallFrame::b_offset() as Immediate12),
op::lw(0x11, 0x11, 0),
op::addi(0x12, 0x11, 32 as Immediate12),
op::call(0x12, 0x10, 0x11, RegId::CGAS),
op::ret(RegId::BAL),
];
let sender_contract_id = test_context
.setup_contract(program, Some((asset_id, initial_internal_balance)), None)
.contract_id;
let (script_ops, offset) = script_with_data_offset!(
data_offset,
vec![
op::movi(0x10, data_offset + 64),
op::call(0x10, RegId::ZERO, RegId::ZERO, RegId::CGAS),
op::ret(RegId::ONE),
],
test_context.tx_offset()
);
let script_data: Vec<u8> = [
asset_id.as_ref(),
dest_contract_id.as_ref(),
Call::new(sender_contract_id, call_amount, offset as Word)
.to_bytes()
.as_slice(),
]
.into_iter()
.flatten()
.copied()
.collect();
let dest_balance = test_context.get_contract_balance(&dest_contract_id, &asset_id);
assert_eq!(dest_balance, 0);
let source_balance =
test_context.get_contract_balance(&sender_contract_id, &asset_id);
assert_eq!(source_balance, initial_internal_balance);
let transfer_tx = test_context
.start_script(script_ops, script_data)
.gas_limit(gas_limit)
.gas_price(0)
.contract_input(sender_contract_id)
.contract_input(dest_contract_id)
.fee_input()
.contract_output(&sender_contract_id)
.contract_output(&dest_contract_id)
.execute();
assert!(!transfer_tx.should_revert());
let dest_balance = test_context.get_contract_balance(&dest_contract_id, &asset_id);
assert_eq!(dest_balance, call_amount);
let source_balance =
test_context.get_contract_balance(&sender_contract_id, &asset_id);
assert_eq!(source_balance, initial_internal_balance - call_amount);
assert_eq!(transfer_tx.receipts()[3].val().unwrap(), 0);
assert_eq!(transfer_tx.receipts()[2].val().unwrap(), call_amount);
}
#[test]
fn internal_transfer_reduces_source_contract_balance_and_increases_destination_contract_balance(
) {
let rng = &mut StdRng::seed_from_u64(2322u64);
let gas_limit = 1_000_000;
let asset_id: AssetId = rng.gen();
let transfer_amount = 500;
let initial_internal_balance = 1_000_000;
let mut test_context = TestBuilder::new(2322u64);
let dest_contract_id = test_context.setup_contract(vec![], None, None).contract_id;
let program = vec![
op::addi(0x10, RegId::FP, CallFrame::a_offset() as Immediate12),
op::lw(0x10, 0x10, 0),
op::addi(0x11, RegId::FP, CallFrame::b_offset() as Immediate12),
op::lw(0x11, 0x11, 0),
op::addi(0x12, 0x11, 32 as Immediate12),
op::tr(0x12, 0x10, 0x11),
op::ret(RegId::ONE),
];
let sender_contract_id = test_context
.setup_contract(program, Some((asset_id, initial_internal_balance)), None)
.contract_id;
let (script_ops, offset) = script_with_data_offset!(
data_offset,
vec![
op::movi(0x10, data_offset + 64),
op::call(0x10, RegId::ZERO, RegId::ZERO, RegId::CGAS),
op::ret(RegId::ONE),
],
test_context.tx_offset()
);
let script_data: Vec<u8> = [
asset_id.as_ref(),
dest_contract_id.as_ref(),
Call::new(sender_contract_id, transfer_amount, offset as Word)
.to_bytes()
.as_slice(),
]
.into_iter()
.flatten()
.copied()
.collect();
let dest_balance = test_context.get_contract_balance(&dest_contract_id, &asset_id);
assert_eq!(dest_balance, 0);
let source_balance =
test_context.get_contract_balance(&sender_contract_id, &asset_id);
assert_eq!(source_balance, initial_internal_balance);
let transfer_tx = test_context
.start_script(script_ops, script_data)
.gas_limit(gas_limit)
.gas_price(0)
.contract_input(sender_contract_id)
.contract_input(dest_contract_id)
.fee_input()
.contract_output(&sender_contract_id)
.contract_output(&dest_contract_id)
.execute();
assert!(!transfer_tx.should_revert());
let dest_balance = test_context.get_contract_balance(&dest_contract_id, &asset_id);
assert_eq!(dest_balance, transfer_amount);
let source_balance =
test_context.get_contract_balance(&sender_contract_id, &asset_id);
assert_eq!(source_balance, initial_internal_balance - transfer_amount);
}
#[test]
fn internal_transfer_cant_exceed_more_than_source_contract_balance() {
let rng = &mut StdRng::seed_from_u64(2322u64);
let gas_limit = 1_000_000;
let asset_id: AssetId = rng.gen();
let transfer_amount = 500;
let initial_internal_balance = 100;
let mut test_context = TestBuilder::new(2322u64);
let dest_contract_id = test_context.setup_contract(vec![], None, None).contract_id;
let program = vec![
op::addi(0x10, RegId::FP, CallFrame::a_offset() as Immediate12),
op::lw(0x10, 0x10, 0),
op::addi(0x11, RegId::FP, CallFrame::b_offset() as Immediate12),
op::lw(0x11, 0x11, 0),
op::addi(0x12, 0x11, 32 as Immediate12),
op::tr(0x12, 0x10, 0x11),
op::ret(RegId::ONE),
];
let sender_contract_id = test_context
.setup_contract(program, Some((asset_id, initial_internal_balance)), None)
.contract_id;
let (script_ops, offset) = script_with_data_offset!(
data_offset,
vec![
op::movi(0x10, data_offset + 64),
op::call(0x10, RegId::ZERO, RegId::ZERO, RegId::CGAS),
op::ret(RegId::ONE),
],
test_context.tx_offset()
);
let script_data: Vec<u8> = [
asset_id.as_ref(),
dest_contract_id.as_ref(),
Call::new(sender_contract_id, transfer_amount, offset as Word)
.to_bytes()
.as_slice(),
]
.into_iter()
.flatten()
.copied()
.collect();
let dest_balance = test_context.get_contract_balance(&dest_contract_id, &asset_id);
assert_eq!(dest_balance, 0);
let source_balance =
test_context.get_contract_balance(&sender_contract_id, &asset_id);
assert_eq!(source_balance, initial_internal_balance);
let transfer_tx = test_context
.start_script(script_ops, script_data)
.gas_limit(gas_limit)
.gas_price(0)
.contract_input(sender_contract_id)
.contract_input(dest_contract_id)
.fee_input()
.contract_output(&sender_contract_id)
.contract_output(&dest_contract_id)
.execute();
assert!(transfer_tx.should_revert());
let dest_balance = test_context.get_contract_balance(&dest_contract_id, &asset_id);
assert_eq!(dest_balance, 0);
let source_balance =
test_context.get_contract_balance(&sender_contract_id, &asset_id);
assert_eq!(source_balance, initial_internal_balance);
}