Authorization and Access
Covered in this guide
Section titled “Covered in this guide”In this guide, you will:
- Understand the importance of authorization and access control in smart contract security.
- Learn how to define and apply
AccessRulesfor components and their methods. - Discover how to set permissions for resources, including minting, burning, and metadata access.
- Explore identity management and the different types of callers in the Tari Ootle environment.
- Follow best practices for securing templates and managing digital assets.
Authorization and access control are critical aspects of smart contract security. The Tari Ootle Template Library provides flexible mechanisms to define who can execute methods on your Components and interact with your Resources.
Access Rules
Section titled “Access Rules”Access rules determine which identities (Components, users, or other templates) are allowed to perform certain actions. They are typically defined when a Component is created or a Resource is minted.
The AccessRules struct is used to configure these permissions.
Component Access Rules
Section titled “Component Access Rules”When you create a Component, you can specify AccessRules for its methods.
use tari_template_lib::prelude::*;
#[template]mod template { pub struct MyComponent { // Component state fields }
pub fn create_with_access_rules( admin_badge: ResourceAddress, ) -> ComponentAddress { let component = MyComponent { /* ... */ }; // Your component instance
let component_address = Component::new(component) .with_access_rules(ComponentAccessRules::new() // Allow anyone to call the 'public_method' .method("public_method", rule!(allow_all)) // Only allow the 'admin_component' to call the 'admin_method' .method("admin_method", rule!(require(admin_badge)]) // Default rule: anyone can call methods not // explicitly listed. Without a default rule it // defaults to `DenyAll` — only the owner can call. .default(rule!(allow_all)) ) .create_component("my_secured_component", component); component_address }}ComponentAccessRules::new(): Creates a new set of component access rules..method("method_name", AccessRule): Defines an access rule for a specific method.AccessRule::AllowAll: Allows any caller to execute the method.AccessRule::Restricted(vec![identity1, identity2, ...]): Only allows callers whose identity matches one of the provided identities. Identities can beComponentAddress,ResourceAddress,TemplateAddress, etc.AccessRule::DenyAll: Denies all callers from executing the method.AccessRule::Owner: Only allows the owner of the component to execute the method. (Note: “owner” is a specific concept often tied to the initial deployer or a designated resource holder).default_access_rule(AccessRule): Sets the default rule for any method not explicitly defined.
Resource Access Rules
Section titled “Resource Access Rules”Resources can also have access rules, typically governing who can mint, burn, recall, freeze, or update metadata. Each builder method takes two arguments:
- The [
AccessRule] that gates the action right now. - An [
UpdateRule] that gates who, if anyone, can later change that access rule. The updater is fixed at creation time and cannot itself be changed afterwards — this lets holders trust aLockedrule will stay locked forever.
use tari_template_lib::prelude::*;
#[template]mod template {
pub struct MyComponent { resource: ResourceManager, }
impl MyComponent { pub fn create_with_restricted_resource( minter_badge: NonFungibleAddress, ) -> ResourceAddress { let resource_address = ResourceBuilder::public_fungible() .with_token_symbol("RTK") .with_divisibility(10) .initial_supply(amount!["1000000000000000000000"]) // Only the minter badge can mint, and only the owner may // change who can mint later. .mintable(rule!(non_fungible(minter_badge)), OWNER) // Nobody can burn, and the rule is locked forever. .burnable(rule!(deny_all), LOCKED) .build();
MyComponent { resource: resource_address.into() }.create() } }}Each ResourceBuilder exposes one method per action. All accept (rule, updater):
.mintable(rule, updater)— who can mint new units of this resource..burnable(rule, updater)— who can burn units..recallable(rule, updater)— who can recall units from any external vault..freezable(rule, updater)— who can freeze a vault holding this resource..withdrawable(rule, updater)— who can withdraw this resource from vaults..depositable(rule, updater)— who can deposit this resource into vaults..update_metadata(rule, updater)— who can change the metadata (the token symbol always stays immutable)..update_non_fungible_data(rule, updater)— who can change the mutable data on individual NFTs (non-fungible only).
UpdateRule
Section titled “UpdateRule”UpdateRule controls who can later change an access rule, and has three variants:
UpdateRule | Constant | Behaviour |
|---|---|---|
UpdateRule::Locked | LOCKED | Nobody — not even the resource owner — can change the rule. |
UpdateRule::Owner | OWNER | Only the resource owner (per the OwnerRule) can change the rule. |
UpdateRule::AccessRule | n/a | Anyone satisfying the inner AccessRule can change the rule. No implicit owner override. |
Why Locked matters — it is the only way to make a trust promise to holders that cannot be quietly retracted
later. If recall is DenyAll but the updater is Owner, the owner can flip recall back on after users have
already accepted the token; the same goes for freeze, mint (inflation), withdraw, and so on. Issuing a token
with the recall/freeze/mint rules Locked is what lets a user verify, by reading the substate once, that those
powers can never be granted to anyone — not even the issuer. Use LOCKED for any property you want holders to be
able to rely on indefinitely, and reserve OWNER (or a more permissive updater) for rules you genuinely expect
to govern over time.
Pass an AccessRule directly where an UpdateRule is expected and it is converted to
UpdateRule::AccessRule(rule):
let admin = rule!(non_fungible(admin_badge));let resource = ResourceBuilder::public_fungible() // Recall is denied today, but the admin badge holder can re-enable it later. .recallable(rule!(deny_all), admin.clone()) // Withdrawals are open, but only the owner can ever change that. .withdrawable(rule!(allow_all), OWNER) .build();Defaults
Section titled “Defaults”Calling ResourceAccessRules::new() (the default) yields safe baseline rules:
mint,burn,recall,freeze,update_metadata:DenyAll, updaterLocked.withdraw,deposit:AllowAll, updaterLocked.update_non_fungible_data:AllowAll, updaterOwner.
Locked-by-default means a resource issued with ResourceAccessRules::new() and no overrides can never have its
recall, mint, etc. silently re-enabled later. To leave room for future tightening or relaxation, pass OWNER
(or an explicit access rule) as the updater on the actions you want to keep editable.
Updating an access rule later
Section titled “Updating an access rule later”Once a resource exists, use ResourceManager::update_access_rule to change a single field. The engine consults
the field’s UpdateRule to decide whether the caller is allowed:
ResourceManager::get(my_resource) .update_access_rule(ResourceAuthAction::Withdraw, rule!(non_fungible(user_badge)));The action is one of ResourceAuthAction::{Mint, Burn, Recall, Withdraw, Deposit, UpdateNonFungibleData, UpdateMetadata, Freeze}.
If the field’s updater is Locked, this call fails even for the owner. If the updater is Owner, only the resource
owner can succeed. If the updater is AccessRule(rule), only a caller satisfying rule can succeed — the owner
has no implicit override.
Identity Management
Section titled “Identity Management”Callers are identified by various types, depending on the context:
ComponentAddress: When a component calls another component.ResourceAddress: When a resource grants specific permissions (e.g., holding a specific NFT grants voting rights).TemplateAddress: When a template has specific rights.- User identities: Through the transaction’s signers or proofs provided.
The auth module in tari_template_lib provides utilities for working with identities and proofs within your templates.
Best Practices
Section titled “Best Practices”- Principle of Least Privilege: Grant only the necessary permissions. Avoid
AllowAllunless it’s genuinely intended for public methods. - Clear Ownership: Define and manage component/resource ownership clearly.
- Test Thoroughly: Always write comprehensive tests to ensure your access rules behave as expected.
By effectively utilizing access rules, you can ensure the security and integrity of your Tari Ootle Templates and the assets they manage.