use tari_engine::runtime::{ActionIdent, NativeAction, RuntimeError};
use tari_engine_types::{
commit_result::RejectReason,
indexed_value::IndexedWellKnownTypes,
resource_container::ResourceError,
};
use tari_ootle_transaction::{Transaction, args};
use tari_template_lib::{
args::VaultAction,
types::{Amount, ComponentAddress, ResourceType, constants::TARI_TOKEN},
};
use tari_template_test_tooling::{TemplateTest, support::assert_error::assert_reject_reason};
const CRATE_PATH: &str = env!("CARGO_MANIFEST_DIR");
const TEMPLATE_NAME: &str = "Shenanigans";
#[test]
fn it_rejects_dangling_vaults_in_constructor() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/shenanigans"]);
let template_addr = test.get_template_address(TEMPLATE_NAME);
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_function(template_addr, "dangling_vault", args![])
.build_and_seal(test.secret_key()),
vec![],
);
assert_reject_reason(
reason,
"1 orphaned substate(s) detected",
);
}
#[test]
fn it_rejects_dangling_vault_that_has_been_returned() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/shenanigans"]);
let template_addr = test.get_template_address(TEMPLATE_NAME);
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_function(template_addr, "return_vault", args![])
.build_and_seal(test.secret_key()),
vec![],
);
assert_reject_reason(reason, "1 orphaned substate(s) detected");
}
#[test]
fn it_rejects_dangling_vaults_in_component() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/shenanigans"]);
let template_addr = test.get_template_address(TEMPLATE_NAME);
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(template_addr, "with_vault", args![])
.build_and_seal(test.secret_key()),
vec![],
);
let component_address = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let component = test.read_only_state_store().get_component(component_address).unwrap();
let indexed = IndexedWellKnownTypes::from_value(component.state()).unwrap();
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(component_address, "drop_vault", args![])
.build_and_seal(test.secret_key()),
vec![test.owner_proof()],
);
assert_reject_reason(reason, RuntimeError::OrphanedSubstate {
id: indexed.vault_ids()[0].into(),
});
}
#[test]
fn it_rejects_dangling_resources() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/shenanigans"]);
let template_addr = test.get_template_address(TEMPLATE_NAME);
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_function(template_addr, "dangling_resource", args![])
.build_and_seal(test.secret_key()),
vec![],
);
assert_reject_reason(reason, "1 orphaned substate(s) detected")
}
#[test]
fn it_rejects_unknown_substate_ids() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/shenanigans"]);
let template_addr = test.get_template_address(TEMPLATE_NAME);
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_function(template_addr, "non_existent_id", args![])
.build_and_seal(test.secret_key()),
vec![],
);
assert_reject_reason(
reason,
RejectReason::SubstateNotFound(
"At instruction #1: Template referenced substate but it was not found: \
resource_abababababababababababababababababababababababababababababababab"
.to_string(),
),
)
}
#[test]
fn it_rejects_references_to_buckets_that_arent_in_scope() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/shenanigans"]);
let template_addr = test.get_template_address(TEMPLATE_NAME);
let (account, owner_token, owner_key) = test.create_funded_account();
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(template_addr, "with_vault", args![])
.build_and_seal(&owner_key),
vec![owner_token.clone()],
);
let shenanigans = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(account, "withdraw", args![TARI_TOKEN, 1000])
.put_last_instruction_output_on_workspace("bucket")
.call_method(shenanigans, "take_bucket_zero", args![])
.build_and_seal(&owner_key),
vec![owner_token],
);
assert_reject_reason(reason, RuntimeError::BucketNotFound { bucket_id: 0.into() });
}
#[test]
fn it_rejects_double_ownership_of_vault() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/shenanigans"]);
let template_addr = test.get_template_address(TEMPLATE_NAME);
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_function(template_addr, "with_vault_copy", args![])
.build_and_seal(test.secret_key()),
vec![test.owner_proof()],
);
assert_reject_reason(reason, "Duplicate reference to substate");
}
#[test]
fn it_prevents_access_to_vault_id_in_component_context() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/shenanigans"]);
let template_addr = test.get_template_address(TEMPLATE_NAME);
let (account, _, _) = test.create_funded_account();
let vault_id = {
let component = test.read_only_state_store().get_component(account).unwrap();
let values = IndexedWellKnownTypes::from_value(component.state()).unwrap();
values.vault_ids()[0]
};
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(template_addr, "with_vault", args![])
.build_and_seal(test.secret_key()),
vec![test.owner_proof()],
);
let shenanigans = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(shenanigans, "take_from_a_vault", args![vault_id, 1000])
.build_and_seal(test.secret_key()),
vec![test.owner_proof()],
);
assert_reject_reason(reason, RuntimeError::SubstateNotOwned {
id: vault_id.into(),
requested_owner: Box::new(shenanigans.into()),
});
}
#[test]
fn it_prevents_access_to_out_of_scope_component() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/shenanigans"]);
let template_addr = test.get_template_address(TEMPLATE_NAME);
let (account, _, _) = test.create_funded_account();
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(template_addr, "new", args![])
.build_and_seal(test.secret_key()),
vec![test.owner_proof()],
);
let shenanigans = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(shenanigans, "empty_state_on_component", args![account])
.build_and_seal(test.secret_key()),
vec![test.owner_proof()],
);
assert_reject_reason(reason, RuntimeError::AccessDeniedSetComponentState {
attempted_on: account.into(),
attempted_by: Box::new(shenanigans.into()),
});
}
#[test]
fn it_disallows_calls_on_vaults_that_are_not_owned_by_current_component() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/shenanigans"]);
let template_addr = test.get_template_address(TEMPLATE_NAME);
let (victim, _, _) = test.create_funded_account();
let (attacker, _, _) = test.create_empty_account();
let vault_id = {
let component = test.read_only_state_store().get_component(victim).unwrap();
let values = IndexedWellKnownTypes::from_value(component.state()).unwrap();
values.vault_ids()[0]
};
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_function(
template_addr,
"attempt_to_steal_funds_using_cross_template_call",
args![vault_id, attacker, Some(Amount::from(1000u64))],
)
.build_and_seal(test.secret_key()),
vec![test.owner_proof()],
);
assert_reject_reason(reason, RuntimeError::NotInComponentContext {
action: VaultAction::Withdraw.into(),
});
}
#[test]
fn it_disallows_vault_access_if_vault_is_not_owned() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/shenanigans"]);
let template_addr = test.get_template_address(TEMPLATE_NAME);
let (victim, _, _) = test.create_funded_account();
let vault_id = {
let component = test.read_only_state_store().get_component(victim).unwrap();
let values = IndexedWellKnownTypes::from_value(component.state()).unwrap();
values.vault_ids()[0]
};
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_function(template_addr, "ref_stolen_vault", args![vault_id])
.build_and_seal(test.secret_key()),
vec![test.owner_proof()],
);
assert_reject_reason(reason, RuntimeError::SubstateOutOfScope { id: vault_id.into() });
}
#[test]
fn it_disallows_minting_different_resource_type() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/resource_shenanigans"]);
let template_addr = test.get_template_address(TEMPLATE_NAME);
let (account, _, _) = test.create_empty_account();
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(template_addr, "new", args![])
.build_and_seal(test.secret_key()),
vec![],
);
let component = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(component, "mint_different_resource_type", args![])
.put_last_instruction_output_on_workspace("bucket")
.call_method(account, "deposit", args![Workspace("bucket")])
.build_and_seal(test.secret_key()),
vec![],
);
assert_reject_reason(reason, ResourceError::ResourceTypeMismatch {
operate: "mint",
expected: ResourceType::NonFungible,
given: ResourceType::Fungible,
});
}
#[test]
fn it_does_not_bring_non_owned_vault_id_into_scope() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/shenanigans"]);
let template_addr = test.get_template_address(TEMPLATE_NAME);
let (account, _, _) = test.create_funded_account();
let vault_id = {
let store = test.read_only_state_store();
let component = store.get_component(account).unwrap();
let values = IndexedWellKnownTypes::from_value(component.state()).unwrap();
values.vault_ids()[0]
};
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_function(template_addr, "with_stolen_vault", args![vault_id])
.put_last_instruction_output_on_workspace("bucket")
.call_method(account, "deposit", args![Workspace("bucket")])
.add_input(vault_id)
.build_and_seal(test.secret_key()),
vec![],
);
assert_reject_reason(reason, RuntimeError::SubstateOutOfScope { id: vault_id.into() });
}
#[test]
fn it_disallows_withdraws_from_vaults_outside_of_component_context() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/shenanigans"]);
let (account, _, _) = test.create_funded_account();
let vault_id = {
let store = test.read_only_state_store();
let component = store.get_component(account).unwrap();
let values = IndexedWellKnownTypes::from_value(component.state()).unwrap();
values.vault_ids()[0]
};
let template_addr = test.compile_new_template(TEMPLATE_NAME, "tests/templates/shenanigans", &[], [(
"VAULT_ID",
vault_id.to_string(),
)]);
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_function(template_addr, "take_from_hardcoded_vault", args![])
.put_last_instruction_output_on_workspace("bucket")
.call_method(account, "deposit", args![Workspace("bucket")])
.add_input(vault_id)
.build_and_seal(test.secret_key()),
vec![],
);
assert_reject_reason(reason, RuntimeError::NotInComponentContext {
action: ActionIdent::Native(NativeAction::Vault(VaultAction::Withdraw)),
});
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_function(template_addr, "take_from_vault_and_return_bucket", args![vault_id,])
.put_last_instruction_output_on_workspace("bucket")
.call_method(account, "deposit", args![Workspace("bucket")])
.add_input(vault_id)
.build_and_seal(test.secret_key()),
vec![],
);
assert_reject_reason(reason, RuntimeError::NotInComponentContext {
action: ActionIdent::Native(NativeAction::Vault(VaultAction::Withdraw)),
});
}
#[test]
fn it_disallows_withdraws_from_vaults_outside_of_owning_component() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/shenanigans"]);
let (account, _, _) = test.create_funded_account();
let vault_id = {
let store = test.read_only_state_store();
let component = store.get_component(account).unwrap();
let values = IndexedWellKnownTypes::from_value(component.state()).unwrap();
values.vault_ids()[0]
};
let template_addr = test.compile_new_template(TEMPLATE_NAME, "tests/templates/shenanigans", &[], [(
"VAULT_ID",
vault_id.to_string(),
)]);
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(template_addr, "new", args![])
.build_and_seal(test.secret_key()),
vec![],
);
let component = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(component, "take_from_hardcoded_vault_in_component_context", args![])
.put_last_instruction_output_on_workspace("bucket")
.call_method(account, "deposit", args![Workspace("bucket")])
.add_input(vault_id)
.build_and_seal(test.secret_key()),
vec![test.owner_proof()],
);
assert_reject_reason(reason, RuntimeError::SubstateNotOwned {
id: vault_id.into(),
requested_owner: Box::new(component.into()),
});
}