use std::collections::{BTreeMap, HashMap};
use tari_engine::runtime::{ActionIdent, LockError, RuntimeError};
use tari_ootle_transaction::{Transaction, args};
use tari_template_lib::{
args::ComponentAction,
types::{
AccessRule,
ComponentAddress,
Metadata,
NonFungibleId,
OwnerRule,
ResourceAddress,
VaultId,
access_rules::{
ComponentAccessRules,
OWNER,
RequireRule,
ResourceAccessRules,
ResourceAuthAction,
RestrictedAccessRule,
},
rule,
},
};
use tari_template_test_tooling::{
TemplateTest,
support::assert_error::{
assert_access_denied_for_action,
assert_insufficient_funds_for_action,
assert_reject_reason,
},
};
const CRATE_PATH: &str = env!("CARGO_MANIFEST_DIR");
mod component_access_rules {
use super::*;
#[test]
fn it_restricts_component_methods() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let (owner1_proof, _, owner1_key) = test.create_owner_proof();
let (owner2_proof, _, owner2_key) = test.create_owner_proof();
let access_rules_template = test.get_template_address("AccessRulesTest");
let owner_rule = AccessRule::Restricted(
RestrictedAccessRule::Require(RequireRule::Require(owner1_proof.clone().into())).or(
RestrictedAccessRule::Require(RequireRule::Require(owner2_proof.clone().into())),
),
);
let component_rules = ComponentAccessRules::new()
.add_method_rule("set_value", owner_rule.clone())
.default(AccessRule::DenyAll);
test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "with_configured_rules", args![
OwnerRule::ByAccessRule(owner_rule),
component_rules,
ResourceAccessRules::deny_all(),
AccessRule::DenyAll,
])
.build_and_seal(&owner1_key),
vec![owner1_proof.clone()],
);
let (component_address, _) = test
.read_only_state_store()
.get_components_by_template_address(access_rules_template)
.unwrap()
.first()
.unwrap()
.clone();
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(component_address, "set_value", args![1])
.build_and_seal(&owner2_key),
vec![owner2_proof],
);
let (unauth_proof, _, unauth_key) = test.create_owner_proof();
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(component_address, "set_value", args![1])
.build_and_seal(&unauth_key),
vec![unauth_proof],
);
assert_access_denied_for_action(reason, ActionIdent::ComponentCallMethod {
component_address,
method: "set_value".to_string(),
});
}
#[test]
fn it_allows_owner_to_update_component_access_rules() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let (owner_proof, _, owner_key) = test.create_owner_proof();
let (user_proof, _, user_key) = test.create_owner_proof();
let access_rules_template = test.get_template_address("AccessRulesTest");
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "with_configured_rules", args![
OwnerRule::OwnedBySigner,
ComponentAccessRules::new().default(AccessRule::DenyAll),
ResourceAccessRules::deny_all(),
AccessRule::DenyAll
])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let component_address = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(component_address, "set_value", args![1])
.build_and_seal(&user_key),
vec![user_proof.clone()],
);
assert_access_denied_for_action(reason, ActionIdent::ComponentCallMethod {
component_address,
method: "set_value".to_string(),
});
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(component_address, "set_component_access_rules", args![
ComponentAccessRules::new()
.add_method_rule("set_value", rule!(non_fungible(user_proof.clone())))
.default(AccessRule::DenyAll)
])
.build_and_seal(&owner_key),
vec![owner_proof],
);
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(component_address, "set_value", args![1])
.build_and_seal(&user_key),
vec![user_proof.clone()],
);
test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(component_address, "set_component_access_rules", args![
ComponentAccessRules::new().default(AccessRule::AllowAll)
])
.build_and_seal(&user_key),
vec![user_proof],
);
}
#[test]
fn it_prevents_access_rule_modification_if_owner_is_none() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let (owner_proof, _, owner_key) = test.create_owner_proof();
let access_rules_template = test.get_template_address("AccessRulesTest");
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "with_configured_rules", args![
OwnerRule::None,
ComponentAccessRules::new().default(AccessRule::AllowAll),
ResourceAccessRules::new(),
AccessRule::DenyAll,
])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let component_address = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(component_address, "set_component_access_rules", args![
ComponentAccessRules::new().default(AccessRule::AllowAll)
])
.build_and_seal(&owner_key),
vec![owner_proof],
);
assert_reject_reason(reason, RuntimeError::AccessDeniedOwnerRequired {
action: ComponentAction::SetAccessRules.into(),
});
}
}
mod resource_access_rules {
use tari_engine::runtime::NativeAction;
use tari_template_lib::{invoke_args, types::Amount};
use super::*;
#[test]
fn it_denies_actions_on_resource() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let (owner_account, owner_proof, owner_key) = test.create_empty_account();
let (user_proof, _, user_key) = test.create_owner_proof();
let access_rules_template = test.get_template_address("AccessRulesTest");
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "with_configured_rules", args![
OwnerRule::OwnedBySigner,
ComponentAccessRules::new().default(AccessRule::AllowAll),
ResourceAccessRules::new().withdrawable(AccessRule::DenyAll, OWNER),
AccessRule::DenyAll,
])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let component_address = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(component_address, "take_tokens", args![10])
.put_last_instruction_output_on_workspace("tokens")
.call_method(owner_account, "deposit", args![Workspace("tokens")])
.build_and_seal(&user_key),
vec![user_proof.clone()],
);
assert_access_denied_for_action(reason, ResourceAuthAction::Withdraw);
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(component_address, "take_tokens", args![10])
.put_last_instruction_output_on_workspace("tokens")
.call_method(owner_account, "deposit", args![Workspace("tokens")])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(component_address, "update_tokens_access_rule", args![
ResourceAuthAction::Withdraw,
rule!(non_fungible(user_proof.clone()))
])
.build_and_seal(&owner_key),
vec![owner_proof],
);
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(component_address, "take_tokens", args![10])
.put_last_instruction_output_on_workspace("tokens")
.call_method(owner_account, "deposit", args![Workspace("tokens")])
.build_and_seal(&user_key),
vec![user_proof],
);
}
#[allow(clippy::too_many_lines)]
#[test]
fn it_denies_recall_for_owner() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let (owner_proof, _, owner_key) = test.create_owner_proof();
let (user_account, user_proof, _) = test.create_empty_account();
let access_rules_template = test.get_template_address("AccessRulesTest");
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "with_configured_rules", args![
OwnerRule::ByAccessRule(AccessRule::AllowAll),
ComponentAccessRules::new().default(AccessRule::AllowAll),
ResourceAccessRules::new().withdrawable(rule!(non_fungible(user_proof.clone())), OWNER),
AccessRule::DenyAll
])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let component_address = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(component_address, "take_badge_by_name", args!["withdraw"])
.put_last_instruction_output_on_workspace("withdraw_perm")
.call_method(component_address, "take_badge_by_name", args!["deposit"])
.put_last_instruction_output_on_workspace("deposit_perm")
.call_method(user_account, "deposit", args![Workspace("withdraw_perm")])
.call_method(user_account, "deposit", args![Workspace("deposit_perm")])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let badge_vault: VaultId = test.extract_component_value(component_address, "$.2");
let badge_resource = *test
.read_only_state_store()
.get_vault(&badge_vault)
.unwrap()
.resource_address();
let vaults: HashMap<ResourceAddress, VaultId> = test.extract_component_value(user_account, "$.0");
let user_badge_vault_id = vaults[&badge_resource];
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(component_address, "recall_badge", args![
user_badge_vault_id,
"withdraw"
])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
assert_access_denied_for_action(reason, ResourceAuthAction::Recall);
}
#[allow(clippy::too_many_lines)]
#[test]
fn it_allows_resource_access_with_badge_then_recall() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let (owner_proof, _, owner_key) = test.create_owner_proof();
let (user_account, user_proof, user_key) = test.create_empty_account();
let access_rules_template = test.get_template_address("AccessRulesTest");
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "using_badge_rules", args![])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let component_address = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let vault: VaultId = test.extract_component_value(component_address, "$.2");
let badge_resource = *test
.read_only_state_store()
.get_vault(&vault)
.unwrap()
.resource_address();
let vault: VaultId = test.extract_component_value(component_address, "$.1");
let token_resource = *test
.read_only_state_store()
.get_vault(&vault)
.unwrap()
.resource_address();
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(component_address, "take_tokens", args![10])
.put_last_instruction_output_on_workspace("tokens")
.call_method(user_account, "deposit", args![Workspace("tokens")])
.build_and_seal(&user_key),
vec![user_proof.clone()],
);
assert_access_denied_for_action(reason, ResourceAuthAction::Withdraw);
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(component_address, "take_badge_by_name", args!["withdraw"])
.put_last_instruction_output_on_workspace("withdraw_perm")
.call_method(component_address, "take_badge_by_name", args!["deposit"])
.put_last_instruction_output_on_workspace("deposit_perm")
.call_method(user_account, "deposit", args![Workspace("withdraw_perm")])
.call_method(user_account, "deposit", args![Workspace("deposit_perm")])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_method(user_account, "create_proof_by_non_fungible_ids", args![
badge_resource,
vec![
NonFungibleId::try_from_string("withdraw").unwrap(),
NonFungibleId::try_from_string("deposit").unwrap()
]
])
.put_last_instruction_output_on_workspace("proof")
.call_method(component_address, "get_nft_data_using_proof", args![Workspace("proof")])
.call_method(component_address, "take_tokens_using_proof", args![
Workspace("proof"),
10
])
.put_last_instruction_output_on_workspace("tokens")
.call_method(user_account, "deposit", args![Workspace("tokens")])
.drop_all_proofs_in_workspace()
.build_and_seal(&user_key),
vec![user_proof.clone()],
);
let badge_data = result.finalize.execution_results[2].decode::<Vec<Metadata>>().unwrap();
assert!(badge_data.iter().all(|b| b.contains_key("colour")));
let vaults: BTreeMap<ResourceAddress, VaultId> = test.extract_component_value(user_account, "$.0");
let user_badge_vault_id = vaults[&badge_resource];
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(component_address, "recall_badge", args![
user_badge_vault_id,
"withdraw"
])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(user_account, "create_proof_for_resource", args![badge_resource])
.put_last_instruction_output_on_workspace("proof")
.call_method(user_account, "withdraw", args![token_resource, 10])
.put_last_instruction_output_on_workspace("tokens")
.call_method(user_account, "deposit", args![Workspace("tokens")])
.drop_all_proofs_in_workspace()
.build_and_seal(&user_key),
vec![user_proof],
);
assert_access_denied_for_action(reason, ResourceAuthAction::Withdraw);
}
#[test]
fn it_allows_access_for_proofs_by_amount() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let (owner_proof, _, owner_key) = test.create_owner_proof();
let (user_account, user_proof, user_key) = test.create_empty_account();
let access_rules_template = test.get_template_address("AccessRulesTest");
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "using_resource_rules", args![])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let access_rules_component = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let badge_resource = result
.finalize
.result
.any_accept()
.unwrap()
.up_iter()
.filter_map(|(addr, s)| s.substate_value().as_resource().map(|r| (addr, r)))
.filter(|(_, r)| r.resource_type().is_non_fungible())
.map(|(addr, _)| addr.as_resource_address().unwrap())
.next()
.unwrap();
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(access_rules_component, "take_tokens", args![10])
.put_last_instruction_output_on_workspace("tokens")
.call_method(user_account, "deposit", args![Workspace("tokens")])
.build_and_seal(&user_key),
vec![user_proof.clone()],
);
assert_access_denied_for_action(reason, ResourceAuthAction::Withdraw);
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(access_rules_component, "mint_new_badge", args![])
.put_last_instruction_output_on_workspace("permission")
.call_method(user_account, "deposit", args![Workspace("permission")])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(user_account, "create_proof_by_amount", args![badge_resource, 1])
.put_last_instruction_output_on_workspace("proof")
.call_method(access_rules_component, "take_tokens_using_proof", args![
Workspace("proof"),
10
])
.put_last_instruction_output_on_workspace("tokens")
.call_method(user_account, "deposit", args![Workspace("tokens")])
.drop_all_proofs_in_workspace()
.build_and_seal(&user_key),
vec![user_proof.clone()],
);
}
#[test]
fn it_locks_resources_used_in_proofs() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let (owner_account, owner_proof, owner_key) = test.create_empty_account();
let access_rules_template = test.get_template_address("AccessRulesTest");
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "with_configured_rules", args![
OwnerRule::OwnedBySigner,
ComponentAccessRules::new(),
ResourceAccessRules::new(),
AccessRule::DenyAll
])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let component_address = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let token_resource = result
.finalize
.result
.any_accept()
.unwrap()
.up_iter()
.filter_map(|(addr, s)| s.substate_value().as_resource().map(|r| (addr, r)))
.filter(|(_, r)| r.resource_type().is_public_fungible())
.map(|(addr, _)| addr.as_resource_address().unwrap())
.next()
.unwrap();
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(component_address, "take_tokens", args![1000])
.put_last_instruction_output_on_workspace("tokens")
.call_method(owner_account, "deposit", args![Workspace("tokens")])
.call_method(owner_account, "create_proof_by_amount", args![token_resource, 1000])
.put_last_instruction_output_on_workspace("proof")
.call_method(owner_account, "withdraw", args![token_resource, 1000])
.put_last_instruction_output_on_workspace("tokens")
.call_method(owner_account, "deposit", args![Workspace("tokens")])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
assert_insufficient_funds_for_action(reason);
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(component_address, "take_tokens", args![1000])
.put_last_instruction_output_on_workspace("tokens")
.call_method(owner_account, "deposit", args![Workspace("tokens")])
.call_method(owner_account, "create_proof_by_amount", args![token_resource, 1000])
.put_last_instruction_output_on_workspace("proof")
.drop_all_proofs_in_workspace()
.call_method(owner_account, "withdraw", args![token_resource, 1000])
.put_last_instruction_output_on_workspace("tokens")
.call_method(owner_account, "deposit", args![Workspace("tokens")])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
}
#[test]
fn it_permits_cross_template_calls_using_proofs() {
let mut test = TemplateTest::new(CRATE_PATH, [
"tests/templates/access_rules",
"tests/templates/cross_template",
]);
let (owner_account, owner_proof, owner_key) = test.create_empty_account();
let access_rules_template = test.get_template_address("AccessRulesTest");
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "using_resource_rules", args![])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let component_address = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let badge_resource = result
.finalize
.result
.any_accept()
.unwrap()
.up_iter()
.filter_map(|(addr, s)| s.substate_value().as_resource().map(|r| (addr, r)))
.filter(|(_, r)| r.resource_type().is_non_fungible())
.map(|(addr, _)| addr.as_resource_address().unwrap())
.next()
.unwrap();
let cross_call_template = test.get_template_address("CrossTemplate");
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_function(cross_call_template, "call_component_with_args", args![
component_address,
"take_tokens",
invoke_args![10],
])
.put_last_instruction_output_on_workspace("tokens")
.call_method(owner_account, "deposit", args![Workspace("tokens")])
.drop_all_proofs_in_workspace()
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
assert_access_denied_for_action(reason, ResourceAuthAction::Withdraw);
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(component_address, "mint_new_badge", args![])
.put_last_instruction_output_on_workspace("badge")
.call_method(owner_account, "deposit", args![Workspace("badge")])
.call_method(owner_account, "create_proof_for_resource", args![badge_resource])
.put_last_instruction_output_on_workspace("proof")
.call_function(cross_call_template, "call_component_with_args_using_proof", args![
component_address,
"take_tokens_using_proof",
Workspace("proof"),
10,
])
.put_last_instruction_output_on_workspace("tokens")
.call_method(owner_account, "deposit", args![Workspace("tokens")])
.drop_all_proofs_in_workspace()
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
}
#[allow(clippy::too_many_lines)]
#[test]
fn it_creates_a_proof_from_bucket() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let (owner_proof, _, owner_key) = test.create_owner_proof();
let (user_account, user_proof, user_key) = test.create_empty_account();
let access_rules_template = test.get_template_address("AccessRulesTest");
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "using_badge_rules", args![])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let component_address = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let badge_resource = result
.finalize
.result
.any_accept()
.unwrap()
.up_iter()
.filter_map(|(addr, s)| s.substate_value().as_resource().map(|r| (addr, r)))
.filter(|(_, r)| r.resource_type().is_non_fungible())
.map(|(addr, _)| addr.as_resource_address().unwrap())
.next()
.unwrap();
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(component_address, "take_tokens", args![10])
.put_last_instruction_output_on_workspace("tokens")
.call_method(user_account, "deposit", args![Workspace("tokens")])
.build_and_seal(&user_key),
vec![user_proof.clone()],
);
assert_access_denied_for_action(reason, ResourceAuthAction::Withdraw);
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(component_address, "take_badge_by_name", args!["withdraw"])
.put_last_instruction_output_on_workspace("withdraw_perm")
.call_method(component_address, "take_badge_by_name", args!["deposit"])
.put_last_instruction_output_on_workspace("deposit_perm")
.call_method(user_account, "deposit", args![Workspace("withdraw_perm")])
.call_method(user_account, "deposit", args![Workspace("deposit_perm")])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(
user_account,
"withdraw_many_non_fungibles",
args![
badge_resource,
vec![
NonFungibleId::try_from_string("withdraw").unwrap(),
NonFungibleId::try_from_string("deposit").unwrap()
]
],
)
.put_last_instruction_output_on_workspace("badges")
.call_function(
access_rules_template,
"create_proof_from_bucket",
args![Workspace("badges")],
)
.put_last_instruction_output_on_workspace("proof")
.call_method(
component_address,
"take_tokens_using_proof",
args![Workspace("proof"), 10],
)
.put_last_instruction_output_on_workspace("tokens")
.call_method(user_account, "deposit", args![Workspace("tokens")])
.call_method(user_account, "deposit", args![Workspace("badges")])
.drop_all_proofs_in_workspace()
.build_and_seal(&owner_key),
vec![user_proof.clone()],
);
assert_reject_reason(reason, RuntimeError::InvalidOpDepositLockedBucket {
bucket_id: 0.into(),
locked_amount: Amount::from(2u64),
});
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(
user_account,
"withdraw_many_non_fungibles",
args![
badge_resource,
vec![
NonFungibleId::try_from_string("withdraw").unwrap(),
NonFungibleId::try_from_string("deposit").unwrap()
]
],
)
.put_last_instruction_output_on_workspace("badges")
.call_function(
access_rules_template,
"create_proof_from_bucket",
args![Workspace("badges")],
)
.put_last_instruction_output_on_workspace("proof")
.call_method(
component_address,
"take_tokens_using_proof",
args![Workspace("proof"), 10],
)
.put_last_instruction_output_on_workspace("tokens")
.call_method(user_account, "deposit", args![Workspace("tokens")])
.drop_all_proofs_in_workspace()
.call_method(user_account, "deposit", args![Workspace("badges")])
.build_and_seal(&owner_key),
vec![user_proof],
);
}
#[test]
fn it_restricts_resource_actions_to_component() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let (owner_account, owner_proof, owner_key) = test.create_empty_account();
let access_rules_template = test.get_template_address("AccessRulesTest");
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(
access_rules_template,
"resource_actions_restricted_to_component",
args![],
)
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let component_address = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let token_resource = result
.finalize
.result
.any_accept()
.unwrap()
.up_iter()
.filter_map(|(addr, s)| s.substate_value().as_resource().map(|r| (addr, r)))
.filter(|(_, r)| r.resource_type().is_public_fungible())
.map(|(addr, _)| addr.as_resource_address().unwrap())
.next()
.unwrap();
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_function(access_rules_template, "mint_resource", args![token_resource])
.put_last_instruction_output_on_workspace("tokens")
.call_method(owner_account, "deposit", args![Workspace("tokens")])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
assert_access_denied_for_action(reason, ResourceAuthAction::Mint);
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(component_address, "mint_more_tokens", args![1000])
.put_last_instruction_output_on_workspace("tokens")
.call_method(owner_account, "deposit", args![Workspace("tokens")])
.build_and_seal(&owner_key),
vec![owner_proof],
);
}
#[test]
fn it_allows_resource_actions_if_auth_hook_passes() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let (owner_account, owner_proof, owner_key) = test.create_empty_account();
let access_rules_template = test.get_template_address("AccessRulesTest");
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "with_auth_hook", args![true, "valid_auth_hook"])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let component_address = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(component_address, "take_tokens", args![10])
.put_last_instruction_output_on_workspace("tokens")
.call_method(owner_account, "deposit", args![Workspace("tokens")])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
}
#[test]
fn it_denies_resource_actions_if_auth_hook_fails() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let (owner_account, owner_proof, owner_key) = test.create_empty_account();
let access_rules_template = test.get_template_address("AccessRulesTest");
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "with_auth_hook", args![false, "valid_auth_hook"])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let component_address = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let result = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(component_address, "take_tokens", args![10])
.put_last_instruction_output_on_workspace("tokens")
.call_method(owner_account, "deposit", args![Workspace("tokens")])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
assert_reject_reason(result, RuntimeError::AccessDeniedAuthHook {
action_ident: ResourceAuthAction::Deposit.into(),
details: "Panic! Access denied for action Deposit".to_string(),
});
}
#[test]
fn it_disallows_hook_that_writes_to_caller_component() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let (_owner_account, owner_proof, owner_key) = test.create_empty_account();
let (user_account, user_proof, user_key) = test.create_empty_account();
let access_rules_template = test.get_template_address("AccessRulesTest");
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "with_auth_hook", args![
true,
"malicious_auth_hook_set_state"
])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let component_address = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let result = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(component_address, "take_tokens", args![10])
.put_last_instruction_output_on_workspace("tokens")
.call_method(user_account, "deposit", args![Workspace("tokens")])
.build_and_seal(&user_key),
vec![user_proof.clone()],
);
assert_reject_reason(result, RuntimeError::AccessDeniedSetComponentState {
attempted_on: user_account.into(),
attempted_by: Box::new(component_address.into()),
});
}
#[test]
fn it_disallows_hook_that_attempts_mutable_call_to_caller() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let (_owner_account, owner_proof, owner_key) = test.create_empty_account();
let (user_account, user_proof, user_key) = test.create_empty_account();
let access_rules_template = test.get_template_address("AccessRulesTest");
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "with_auth_hook", args![
true,
"malicious_auth_hook_call_mut"
])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let component_address = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let result = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(component_address, "take_tokens", args![10])
.put_last_instruction_output_on_workspace("tokens")
.call_method(user_account, "deposit", args![Workspace("tokens")])
.build_and_seal(&user_key),
vec![user_proof.clone()],
);
assert_reject_reason(
result,
RuntimeError::LockError(LockError::MultipleWriteLockRequested {
address: user_account.into(),
}),
);
}
#[test]
fn it_disallows_hook_that_attempts_mutable_call_to_another_component_in_the_transaction() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules", "tests/templates/state"]);
let (_owner_account, owner_proof, owner_key) = test.create_empty_account();
let (user_account, user_proof, user_key) = test.create_empty_account();
let state_template = test.get_template_address("State");
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(state_template, "restricted", args![])
.build_and_seal(&user_key),
vec![owner_proof.clone()],
);
let state_component = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let access_rules_template = test.get_template_address("AccessRulesTest");
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "with_auth_hook_attack_component", args![
state_component
])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let component_address = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let result = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(component_address, "take_tokens", args![10])
.put_last_instruction_output_on_workspace("tokens")
.call_method(state_component, "set", args![1])
.call_method(user_account, "deposit", args![Workspace("tokens")])
.build_and_seal(&user_key),
vec![user_proof.clone()],
);
assert_reject_reason(result, RuntimeError::AccessDeniedAuthHook {
action_ident: ResourceAuthAction::Deposit.into(),
details: String::new(),
});
}
#[test]
fn it_fails_if_auth_hook_is_invalid() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let access_rules_template = test.get_template_address("AccessRulesTest");
[
"invalid_auth_hook1",
"invalid_auth_hook2",
"invalid_auth_hook3",
"invalid_auth_hook4",
"invalid_auth_hook5",
"hook_doesnt_exist",
]
.iter()
.for_each(|hook| {
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_function(access_rules_template, "with_auth_hook", args![true, hook])
.build_and_seal(test.secret_key()),
vec![test.owner_proof()],
);
assert_reject_reason(reason, RuntimeError::InvalidArgument {
argument: "CreateResourceArg",
reason: "Authorize hook".to_string(),
});
})
}
#[test]
fn update_metadata_denied_for_non_owner() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let (_, owner_proof, owner_key) = test.create_empty_account();
let (user_proof, _, user_key) = test.create_owner_proof();
let access_rules_template = test.get_template_address("AccessRulesTest");
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "with_configured_rules", args![
OwnerRule::OwnedBySigner,
ComponentAccessRules::new().default(AccessRule::AllowAll),
ResourceAccessRules::new(),
AccessRule::DenyAll,
])
.build_and_seal(&owner_key),
vec![owner_proof],
);
let component_address = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let mut new_metadata = Metadata::new();
new_metadata.insert("description", "updated");
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(component_address, "set_tokens_metadata", args![new_metadata])
.build_and_seal(&user_key),
vec![user_proof],
);
assert_access_denied_for_action(reason, ResourceAuthAction::UpdateMetadata);
}
#[test]
fn update_metadata_allowed_by_custom_rule() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let (_, owner_proof, owner_key) = test.create_empty_account();
let (user_proof, _, user_key) = test.create_owner_proof();
let access_rules_template = test.get_template_address("AccessRulesTest");
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "with_configured_rules", args![
OwnerRule::OwnedBySigner,
ComponentAccessRules::new().default(AccessRule::AllowAll),
ResourceAccessRules::new().update_metadata(rule!(non_fungible(user_proof.clone())), OWNER),
AccessRule::DenyAll,
])
.build_and_seal(&owner_key),
vec![owner_proof],
);
let component_address = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
let mut new_metadata = Metadata::new();
new_metadata.insert("description", "updated by user");
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(component_address, "set_tokens_metadata", args![new_metadata])
.build_and_seal(&user_key),
vec![user_proof],
);
}
#[test]
fn owner_can_update_owner_gated_rule() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let (owner_proof, _, owner_key) = test.create_owner_proof();
let (owner_account, _, _) = test.create_empty_account();
let (user_proof, _, user_key) = test.create_owner_proof();
let access_rules_template = test.get_template_address("AccessRulesTest");
let result = test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "with_configured_rules", args![
OwnerRule::OwnedBySigner,
ComponentAccessRules::new().default(AccessRule::AllowAll),
ResourceAccessRules::new().withdrawable(AccessRule::DenyAll, OWNER),
AccessRule::DenyAll,
])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let component_address = result.finalize.execution_results[0]
.decode::<ComponentAddress>()
.unwrap();
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(component_address, "update_tokens_access_rule", args![
ResourceAuthAction::Withdraw,
rule!(non_fungible(user_proof.clone()))
])
.build_and_seal(&owner_key),
vec![owner_proof],
);
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(component_address, "take_tokens", args![10])
.put_last_instruction_output_on_workspace("tokens")
.call_method(owner_account, "deposit", args![Workspace("tokens")])
.build_and_seal(&user_key),
vec![user_proof],
);
}
#[test]
fn owner_cannot_update_locked_rule() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let (owner_proof, _, owner_key) = test.create_owner_proof();
let access_rules_template = test.get_template_address("AccessRulesTest");
test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "with_configured_rules", args![
OwnerRule::OwnedBySigner,
ComponentAccessRules::new().default(AccessRule::AllowAll),
ResourceAccessRules::new(),
AccessRule::DenyAll,
])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let (component_address, _) = test
.read_only_state_store()
.get_components_by_template_address(access_rules_template)
.unwrap()
.pop()
.unwrap();
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(component_address, "update_tokens_access_rule", args![
ResourceAuthAction::Mint,
AccessRule::AllowAll
])
.build_and_seal(&owner_key),
vec![owner_proof],
);
assert_reject_reason(reason, RuntimeError::AccessDenied {
action_ident: ActionIdent::Native(NativeAction::UpdateResourceAccessRule(ResourceAuthAction::Mint)),
});
}
#[test]
fn badge_holder_can_update_access_rule_gated_rule() {
let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/access_rules"]);
let (owner_proof, _, owner_key) = test.create_owner_proof();
let (owner_account, _, _) = test.create_empty_account();
let (user_proof, _, user_key) = test.create_owner_proof();
let access_rules_template = test.get_template_address("AccessRulesTest");
let updater_rule = rule!(non_fungible(user_proof.clone()));
test.execute_expect_success(
Transaction::builder_localnet()
.call_function(access_rules_template, "with_configured_rules", args![
OwnerRule::OwnedBySigner,
ComponentAccessRules::new().default(AccessRule::AllowAll),
ResourceAccessRules::new().withdrawable(AccessRule::DenyAll, updater_rule),
AccessRule::DenyAll,
])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
let (component_address, _) = test
.read_only_state_store()
.get_components_by_template_address(access_rules_template)
.unwrap()
.pop()
.unwrap();
let reason = test.execute_expect_failure(
Transaction::builder_localnet()
.call_method(component_address, "update_tokens_access_rule", args![
ResourceAuthAction::Withdraw,
AccessRule::AllowAll
])
.build_and_seal(&owner_key),
vec![owner_proof.clone()],
);
assert_reject_reason(reason, RuntimeError::AccessDenied {
action_ident: ActionIdent::Native(NativeAction::UpdateResourceAccessRule(ResourceAuthAction::Withdraw)),
});
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(component_address, "update_tokens_access_rule", args![
ResourceAuthAction::Withdraw,
AccessRule::AllowAll
])
.build_and_seal(&user_key),
vec![user_proof.clone()],
);
test.execute_expect_success(
Transaction::builder_localnet()
.call_method(component_address, "take_tokens", args![10])
.put_last_instruction_output_on_workspace("tokens")
.call_method(owner_account, "deposit", args![Workspace("tokens")])
.build_and_seal(&user_key),
vec![user_proof],
);
}
}