use crate::logic::tests::helpers::*;
use crate::logic::tests::vm_logic_builder::{TestVMLogic, VMLogicBuilder};
use crate::logic::types::Gas;
use crate::logic::MemSlice;
use crate::logic::{HostError, VMLogicError};
use crate::tests::test_vm_config;
use expect_test::expect;
use unc_parameters::{ActionCosts, ExtCosts, Fee};
#[test]
fn test_dont_burn_gas_when_exceeding_attached_gas_limit() {
let gas_limit = 10u64.pow(14);
let mut logic_builder = VMLogicBuilder::default();
logic_builder.config.limit_config.max_gas_burnt = gas_limit * 2;
logic_builder.context.prepaid_gas = gas_limit;
let mut logic = logic_builder.build();
let index = promise_create(&mut logic, b"rick.test", 0, 0).expect("should create a promise");
promise_batch_action_function_call(&mut logic, index, 0, gas_limit * 2)
.expect_err("should fail with gas limit");
let outcome = logic.compute_outcome();
assert!(outcome.burnt_gas < gas_limit / 2);
assert_eq!(outcome.used_gas, gas_limit);
}
#[test]
fn test_limit_wasm_gas_after_attaching_gas() {
let gas_limit = 10u64.pow(14);
let op_limit = op_limit(gas_limit);
let mut logic_builder = VMLogicBuilder::default();
logic_builder.config.limit_config.max_gas_burnt = gas_limit * 2;
logic_builder.context.prepaid_gas = gas_limit;
let mut logic = logic_builder.build();
let index = promise_create(&mut logic, b"rick.test", 0, 0).expect("should create a promise");
promise_batch_action_function_call(&mut logic, index, 0, gas_limit / 2)
.expect("should add action to receipt");
logic.gas_opcodes((op_limit / 2) as u32).expect_err("should fail with gas limit");
let outcome = logic.compute_outcome();
assert_eq!(outcome.used_gas, gas_limit);
assert!(gas_limit / 2 < outcome.burnt_gas);
assert!(outcome.burnt_gas < gas_limit);
}
#[test]
fn test_cant_burn_more_than_max_gas_burnt_gas() {
let gas_limit = 10u64.pow(14);
let op_limit = op_limit(gas_limit);
let mut logic_builder = VMLogicBuilder::default();
logic_builder.config.limit_config.max_gas_burnt = gas_limit;
logic_builder.context.prepaid_gas = gas_limit * 2;
let mut logic = logic_builder.build();
logic.gas_opcodes(op_limit * 3).expect_err("should fail with gas limit");
let outcome = logic.compute_outcome();
assert_eq!(outcome.burnt_gas, gas_limit);
assert_eq!(outcome.used_gas, gas_limit * 2);
}
#[test]
fn test_cant_burn_more_than_prepaid_gas() {
let gas_limit = 10u64.pow(14);
let op_limit = op_limit(gas_limit);
let mut logic_builder = VMLogicBuilder::default();
logic_builder.config.limit_config.max_gas_burnt = gas_limit * 2;
logic_builder.context.prepaid_gas = gas_limit;
let mut logic = logic_builder.build();
logic.gas_opcodes(op_limit * 3).expect_err("should fail with gas limit");
let outcome = logic.compute_outcome();
assert_eq!(outcome.burnt_gas, gas_limit);
assert_eq!(outcome.used_gas, gas_limit);
}
#[test]
fn test_hit_max_gas_burnt_limit() {
let gas_limit = 10u64.pow(14);
let op_limit = op_limit(gas_limit);
let mut logic_builder = VMLogicBuilder::default();
logic_builder.config.limit_config.max_gas_burnt = gas_limit;
logic_builder.context.prepaid_gas = gas_limit * 3;
let mut logic = logic_builder.build();
promise_create(&mut logic, b"rick.test", 0, gas_limit / 2).expect("should create a promise");
logic.gas_opcodes(op_limit * 2).expect_err("should fail with gas limit");
let outcome = logic.compute_outcome();
assert_eq!(outcome.burnt_gas, gas_limit);
assert!(outcome.used_gas > gas_limit * 2);
}
#[test]
fn test_hit_prepaid_gas_limit() {
let gas_limit = 10u64.pow(14);
let op_limit = op_limit(gas_limit);
let mut logic_builder = VMLogicBuilder::default();
logic_builder.config.limit_config.max_gas_burnt = gas_limit * 3;
logic_builder.context.prepaid_gas = gas_limit;
let mut logic = logic_builder.build();
promise_create(&mut logic, b"rick.test", 0, gas_limit / 2).expect("should create a promise");
logic.gas_opcodes(op_limit * 2).expect_err("should fail with gas limit");
let outcome = logic.compute_outcome();
assert_eq!(outcome.burnt_gas, gas_limit);
assert_eq!(outcome.used_gas, gas_limit);
}
#[test]
fn function_call_no_weight_refund() {
let gas_limit = 10u64.pow(14);
let mut logic_builder = VMLogicBuilder::default();
logic_builder.config.limit_config.max_gas_burnt = gas_limit;
logic_builder.context.prepaid_gas = gas_limit;
let mut logic = logic_builder.build();
let index = promise_batch_create(&mut logic, "rick.test").expect("should create a promise");
promise_batch_action_function_call_weight(&mut logic, index, 0, 1000, 0)
.expect("batch action function call should succeed");
let outcome = logic.compute_outcome();
assert!(outcome.used_gas < gas_limit);
}
#[test]
fn test_overflowing_burn_gas_with_promises_gas() {
let gas_limit = 3 * 10u64.pow(14);
let mut logic_builder = VMLogicBuilder::default();
logic_builder.config.limit_config.max_gas_burnt = gas_limit;
logic_builder.context.prepaid_gas = gas_limit;
let mut logic = logic_builder.build();
let account_id = logic.internal_mem_write(b"rick.test");
let args = logic.internal_mem_write(b"");
let num_100u128 = logic.internal_mem_write(&100u128.to_le_bytes());
let num_10u128 = logic.internal_mem_write(&10u128.to_le_bytes());
let index = promise_batch_create(&mut logic, "rick.test").expect("should create a promise");
logic.promise_batch_action_transfer(index, num_100u128.ptr).unwrap();
let call_id = logic.promise_batch_then(index, account_id.len, account_id.ptr).unwrap();
let needed_gas_charge = u64::max_value() - logic.gas_counter().used_gas() - 1;
let function_name_len =
needed_gas_charge / logic.config().ext_costs.gas_cost(ExtCosts::read_memory_byte);
let result = logic.promise_batch_action_function_call(
call_id,
function_name_len,
0,
args.len,
args.ptr,
num_10u128.ptr,
10000,
);
assert!(matches!(
result,
Err(crate::logic::VMLogicError::HostError(crate::logic::HostError::GasLimitExceeded))
));
assert_eq!(logic.gas_counter().used_gas(), gas_limit);
}
#[test]
fn test_overflowing_burn_gas_with_promises_gas_2() {
let gas_limit = 3 * 10u64.pow(14);
let mut logic_builder = VMLogicBuilder::default();
logic_builder.config.limit_config.max_gas_burnt = gas_limit;
logic_builder.context.prepaid_gas = gas_limit / 2;
let mut logic = logic_builder.build();
let account_id = logic.internal_mem_write(b"rick.test");
let args = logic.internal_mem_write(b"");
let num_100u128 = logic.internal_mem_write(&100u128.to_le_bytes());
let index = promise_batch_create(&mut logic, "rick.test").expect("should create a promise");
logic.promise_batch_action_transfer(index, num_100u128.ptr).unwrap();
logic.promise_batch_then(index, account_id.len, account_id.ptr).unwrap();
let minimum_prepay = logic.gas_counter().used_gas();
logic_builder.context.prepaid_gas = minimum_prepay;
let mut logic = logic_builder.build();
let index = promise_batch_create(&mut logic, "rick.test").expect("should create a promise");
logic.promise_batch_action_transfer(index, num_100u128.ptr).unwrap();
let call_id = logic.promise_batch_then(index, account_id.len, account_id.ptr).unwrap();
let needed_gas_charge = u64::max_value() - logic.gas_counter().used_gas() - 1;
let function_name_len =
needed_gas_charge / logic.config().ext_costs.gas_cost(ExtCosts::read_memory_byte);
let result = logic.promise_batch_action_function_call(
call_id,
function_name_len,
0,
args.len,
args.ptr,
10u128.to_le_bytes().as_ptr() as _,
10000,
);
assert!(matches!(
result,
Err(crate::logic::VMLogicError::HostError(crate::logic::HostError::GasExceeded))
));
assert_eq!(logic.gas_counter().used_gas(), minimum_prepay);
}
#[track_caller]
fn check_action_gas_exceeds_limit(
cost: ActionCosts,
num_action_paid: u64,
exercise_action: impl FnOnce(&mut TestVMLogic) -> Result<(), VMLogicError>,
) {
let gas_limit = 10u64.pow(13);
let gas_attached = gas_limit;
let fee = Fee {
send_sir: gas_limit / num_action_paid + 1,
send_not_sir: gas_limit / num_action_paid + 10,
execution: 1, };
let mut logic_builder = VMLogicBuilder::default();
logic_builder.config.limit_config.max_gas_burnt = gas_limit;
logic_builder.fees_config.action_fees[cost] = fee;
logic_builder.context.prepaid_gas = gas_attached;
logic_builder.context.output_data_receivers = vec!["alice.test".parse().unwrap()];
let mut logic = logic_builder.build();
let result = exercise_action(&mut logic);
assert!(result.is_err(), "expected out-of-gas error for {cost:?} but was ok");
assert_eq!(result.unwrap_err(), VMLogicError::HostError(HostError::GasLimitExceeded));
assert_eq!(
gas_attached,
logic.gas_counter().burnt_gas(),
"burnt gas should be all attached gas",
);
assert_eq!(
gas_attached,
logic.gas_counter().used_gas(),
"used gas should be no more than burnt gas",
);
}
#[track_caller]
fn check_action_gas_exceeds_attached(
cost: ActionCosts,
num_action_paid: u64,
expected: expect_test::Expect,
exercise_action: impl FnOnce(&mut TestVMLogic) -> Result<(), VMLogicError>,
) {
let gas_limit = 10u64.pow(14);
let gas_attached = 10u64.pow(13);
let fee = Fee {
send_sir: 1, send_not_sir: 10, execution: gas_attached / num_action_paid + 1,
};
let mut logic_builder = VMLogicBuilder::default();
logic_builder.config.limit_config.max_gas_burnt = gas_limit;
logic_builder.fees_config.action_fees[cost] = fee;
logic_builder.context.prepaid_gas = gas_attached;
logic_builder.context.output_data_receivers = vec!["alice.test".parse().unwrap()];
let mut logic = logic_builder.build();
let result = exercise_action(&mut logic);
assert!(result.is_err(), "expected out-of-gas error for {cost:?} but was ok");
assert_eq!(result.unwrap_err(), VMLogicError::HostError(HostError::GasExceeded));
let actual = format!(
"{} burnt {} used",
logic.gas_counter().burnt_gas(),
logic.gas_counter().used_gas()
);
expected.assert_eq(&actual);
}
#[test]
fn out_of_gas_new_action_receipt() {
check_action_gas_exceeds_limit(ActionCosts::new_action_receipt, 1, create_action_receipt);
check_action_gas_exceeds_limit(ActionCosts::new_action_receipt, 2, create_promise_dependency);
check_action_gas_exceeds_attached(
ActionCosts::new_action_receipt,
1,
expect!["8644846690 burnt 10000000000000 used"],
create_action_receipt,
);
check_action_gas_exceeds_attached(
ActionCosts::new_action_receipt,
2,
expect!["9411968532130 burnt 10000000000000 used"],
create_promise_dependency,
);
fn create_action_receipt(logic: &mut TestVMLogic) -> Result<(), VMLogicError> {
promise_batch_create(logic, "rick.test")?;
Ok(())
}
}
#[test]
fn out_of_gas_new_data_receipt() {
check_action_gas_exceeds_limit(
ActionCosts::new_data_receipt_base,
1,
create_promise_dependency,
);
check_action_gas_exceeds_attached(
ActionCosts::new_data_receipt_base,
1,
expect!["10000000000000 burnt 10000000000000 used"],
create_promise_dependency,
);
}
#[test]
fn out_of_gas_new_data_receipt_byte() {
check_action_gas_exceeds_limit(ActionCosts::new_data_receipt_byte, 11, value_return);
check_action_gas_exceeds_attached(
ActionCosts::new_data_receipt_byte,
11,
expect!["10000000000000 burnt 10000000000000 used"],
value_return,
);
fn value_return(logic: &mut TestVMLogic) -> Result<(), VMLogicError> {
let value = logic.internal_mem_write(b"lorem ipsum");
logic.value_return(11, value.ptr)?;
Ok(())
}
}
#[test]
fn out_of_gas_create_account() {
check_action_gas_exceeds_limit(ActionCosts::create_account, 1, create_account);
check_action_gas_exceeds_attached(
ActionCosts::create_account,
1,
expect!["116969114801 burnt 10000000000000 used"],
create_account,
);
fn create_account(logic: &mut TestVMLogic) -> Result<(), VMLogicError> {
let account_id = "rick.test";
let idx = promise_batch_create(logic, account_id)?;
logic.promise_batch_action_create_account(idx)?;
Ok(())
}
}
#[test]
fn out_of_gas_delete_account() {
check_action_gas_exceeds_limit(ActionCosts::delete_account, 1, delete_account);
check_action_gas_exceeds_attached(
ActionCosts::delete_account,
1,
expect!["125349193370 burnt 10000000000000 used"],
delete_account,
);
fn delete_account(logic: &mut TestVMLogic) -> Result<(), VMLogicError> {
let beneficiary_account_id = "alice.test";
let deleted_account_id = "bob.test";
let idx = promise_batch_create(logic, deleted_account_id)?;
let beneficiary = logic.internal_mem_write(beneficiary_account_id.as_bytes());
logic.promise_batch_action_delete_account(idx, beneficiary.len, beneficiary.ptr)?;
Ok(())
}
}
#[test]
fn out_of_gas_deploy_contract_base() {
check_action_gas_exceeds_limit(ActionCosts::deploy_contract_base, 1, deploy_contract);
check_action_gas_exceeds_attached(
ActionCosts::deploy_contract_base,
1,
expect!["119677812659 burnt 10000000000000 used"],
deploy_contract,
);
}
#[test]
fn out_of_gas_deploy_contract_byte() {
check_action_gas_exceeds_limit(ActionCosts::deploy_contract_byte, 26, deploy_contract);
check_action_gas_exceeds_attached(
ActionCosts::deploy_contract_byte,
26,
expect!["304443562909 burnt 10000000000000 used"],
deploy_contract,
);
}
fn deploy_contract(logic: &mut TestVMLogic) -> Result<(), VMLogicError> {
let account_id = "rick.test";
let idx = promise_batch_create(logic, account_id)?;
let code = logic.internal_mem_write(b"lorem ipsum with length 26");
logic.promise_batch_action_deploy_contract(idx, code.len, code.ptr)?;
Ok(())
}
#[test]
fn out_of_gas_function_call_base() {
check_action_gas_exceeds_limit(ActionCosts::function_call_base, 1, cross_contract_call);
check_action_gas_exceeds_limit(
ActionCosts::function_call_base,
1,
cross_contract_call_gas_weight,
);
check_action_gas_exceeds_attached(
ActionCosts::function_call_base,
1,
expect!["125011579049 burnt 10000000000000 used"],
cross_contract_call,
);
check_action_gas_exceeds_attached(
ActionCosts::function_call_base,
1,
expect!["125011579049 burnt 10000000000000 used"],
cross_contract_call_gas_weight,
);
}
#[test]
fn out_of_gas_function_call_byte() {
check_action_gas_exceeds_limit(ActionCosts::function_call_byte, 40, cross_contract_call);
check_action_gas_exceeds_limit(
ActionCosts::function_call_byte,
40,
cross_contract_call_gas_weight,
);
check_action_gas_exceeds_attached(
ActionCosts::function_call_byte,
40,
expect!["2444873079439 burnt 10000000000000 used"],
cross_contract_call,
);
check_action_gas_exceeds_attached(
ActionCosts::function_call_byte,
40,
expect!["2444873079439 burnt 10000000000000 used"],
cross_contract_call_gas_weight,
);
}
fn cross_contract_call(logic: &mut TestVMLogic) -> Result<(), VMLogicError> {
let account_id = "rick.test";
let idx = promise_batch_create(logic, account_id)?;
let arg = b"lorem ipsum with length 26";
let name = b"fn_with_len_14";
let attached_balance = 1u128;
let gas = 1; promise_batch_action_function_call_ext(logic, idx, name, arg, attached_balance, gas)?;
Ok(())
}
fn cross_contract_call_gas_weight(logic: &mut TestVMLogic) -> Result<(), VMLogicError> {
let account_id = "rick.test";
let idx = promise_batch_create(logic, account_id)?;
let arg = b"lorem ipsum with length 26";
let name = b"fn_with_len_14";
let attached_balance = 1u128;
let gas = 1; let gas_weight = 1;
promise_batch_action_function_call_weight_ext(
logic,
idx,
name,
arg,
attached_balance,
gas,
gas_weight,
)?;
Ok(())
}
#[test]
fn out_of_gas_transfer() {
check_action_gas_exceeds_limit(ActionCosts::transfer, 1, promise_transfer);
check_action_gas_exceeds_attached(
ActionCosts::transfer,
1,
expect!["119935181141 burnt 10000000000000 used"],
promise_transfer,
);
fn promise_transfer(logic: &mut TestVMLogic) -> Result<(), VMLogicError> {
let account_id = "alice.test";
let idx = promise_batch_create(logic, account_id)?;
let attached_balance = logic.internal_mem_write(&1u128.to_be_bytes());
logic.promise_batch_action_transfer(idx, attached_balance.ptr)?;
Ok(())
}
}
#[test]
fn out_of_gas_pledge() {
check_action_gas_exceeds_limit(ActionCosts::pledge, 1, promise_pledge);
check_action_gas_exceeds_attached(
ActionCosts::pledge,
1,
expect!["122375106518 burnt 10000000000000 used"],
promise_pledge,
);
fn promise_pledge(logic: &mut TestVMLogic) -> Result<(), VMLogicError> {
let account_id = "pool.test";
let idx = promise_batch_create(logic, account_id)?;
let attached_balance = logic.internal_mem_write(&1u128.to_be_bytes());
let pk = write_test_pk(logic);
logic.promise_batch_action_stake(idx, attached_balance.ptr, pk.len, pk.ptr)?;
Ok(())
}
}
#[test]
fn out_of_gas_add_full_access_key() {
check_action_gas_exceeds_limit(ActionCosts::add_full_access_key, 1, promise_full_access_key);
check_action_gas_exceeds_attached(
ActionCosts::add_full_access_key,
1,
expect!["119999803802 burnt 10000000000000 used"],
promise_full_access_key,
);
fn promise_full_access_key(logic: &mut TestVMLogic) -> Result<(), VMLogicError> {
let account_id = "alice.test";
let idx = promise_batch_create(logic, account_id)?;
let pk = test_pk();
let nonce = 0;
promise_batch_action_add_key_with_full_access(logic, idx, &pk, nonce)?;
Ok(())
}
}
#[test]
fn out_of_gas_add_function_call_key_base() {
check_action_gas_exceeds_limit(
ActionCosts::add_function_call_key_base,
1,
promise_function_key,
);
check_action_gas_exceeds_attached(
ActionCosts::add_function_call_key_base,
1,
expect!["133982421242 burnt 10000000000000 used"],
promise_function_key,
);
}
#[test]
fn out_of_gas_add_function_call_key_byte() {
check_action_gas_exceeds_limit(
ActionCosts::add_function_call_key_byte,
7,
promise_function_key,
);
check_action_gas_exceeds_attached(
ActionCosts::add_function_call_key_byte,
7,
expect!["236200046312 burnt 10000000000000 used"],
promise_function_key,
);
}
fn promise_function_key(logic: &mut TestVMLogic) -> Result<(), VMLogicError> {
let account_id = "alice.test";
let idx = promise_batch_create(logic, account_id)?;
let allowance = 1u128;
let pk = test_pk();
let nonce = 0;
let methods = b"foo,baz";
promise_batch_action_add_key_with_function_call(
logic,
idx,
&pk,
nonce,
allowance,
account_id.as_bytes(),
methods,
)?;
Ok(())
}
#[test]
fn out_of_gas_delete_key() {
check_action_gas_exceeds_limit(ActionCosts::delete_key, 1, promise_delete_key);
check_action_gas_exceeds_attached(
ActionCosts::delete_key,
1,
expect!["119999803802 burnt 10000000000000 used"],
promise_delete_key,
);
fn promise_delete_key(logic: &mut TestVMLogic) -> Result<(), VMLogicError> {
let account_id = "alice.test";
let idx = promise_batch_create(logic, account_id)?;
let pk = write_test_pk(logic);
logic.promise_batch_action_delete_key(idx, pk.len, pk.ptr)?;
Ok(())
}
}
fn create_promise_dependency(logic: &mut TestVMLogic) -> Result<(), VMLogicError> {
let account_id = "rick.test";
let idx = promise_batch_create(logic, account_id)?;
let account_id = logic.internal_mem_write(account_id.as_bytes());
logic.promise_batch_then(idx, account_id.len, account_id.ptr)?;
Ok(())
}
fn op_limit(gas_limit: Gas) -> u32 {
(gas_limit / (test_vm_config().regular_op_cost as u64)) as u32
}
fn test_pk() -> Vec<u8> {
let pk = borsh::to_vec(
&"ed25519:22W5rKuvbMRphnDoCj6nfrWhRKvh9Xf9SWXfGHaeXGde"
.parse::<unc_crypto::PublicKey>()
.unwrap(),
)
.unwrap();
pk
}
fn write_test_pk(logic: &mut TestVMLogic) -> MemSlice {
logic.internal_mem_write(&test_pk())
}