Access Control
Overview
The Access Control module provides a comprehensive role-based access control system for Soroban contracts. It enables developers to manage permissions through a hierarchical role system, with a renounceable single overarching admin and customizable role assignments.
Key Concepts
Admin Management
The system features a single top-level admin with privileges to call any function in the AccessControl
trait. This admin must be set during contract initialization for the module to function properly. This overarching admin can renounce themselves for decentralization purposes.
Admin transfers are implemented as a two-step process to prevent accidental or malicious takeovers:
-
The current admin initiates the transfer by specifying the new admin and an expiration time (
live_until_ledger
). -
The designated new admin must explicitly accept the transfer to complete it.
Until the transfer is accepted, the original admin retains full control and can override or cancel the transfer by initiating a new one or using a live_until_ledger
of 0
.
Role Hierarchy
The module supports a hierarchical role system where each role can have an "admin role" assigned to it. For example:
-
Create roles
minter
andminter_admin
-
Assign
minter_admin
as the admin role for theminter
role -
Accounts with the
minter_admin
role can grant/revoke theminter
role to other accounts
This allows for creating complex organizational structures with chains of command and delegated authority.
Role Enumeration
The system tracks account-role pairs in storage with additional enumeration logic:
-
When a role is granted to an account, the pair is stored and added to enumeration storage
-
When a role is revoked, the pair is removed from storage and enumeration
-
If all accounts are removed from a role, the helper storage items become empty or 0
Roles exist only through their relationships with accounts, so a role with zero accounts is indistinguishable from a role that never existed.
== Usage Example Here's a simple example of using the Access Control module: [source,rust]
use soroban_sdk::{contract, contractimpl, symbol_short, Address, Env}; use stellar_access_control::{self as access_control, AccessControl}; use stellar_access_control_macros::{has_role, only_admin};
#[contract] pub struct MyContract;
#[contractimpl] impl MyContract { pub fn __constructor(e: &Env, admin: Address) { // Set the contract admin access_control::set_admin(e, &admin);
// Create a "minter" role with admin as its admin access_control::set_role_admin_no_auth(e, &symbol_short!("minter"), &symbol_short!("admin")); }
#[only_admin] pub fn admin_restricted_function(e: &Env) -> Vec<String> { vec![&e, String::from_str(e, "seems sus")] }
// we want `require_auth()` provided by the macro, since there is no // `require_auth()` in `Base::mint`. #[only_role(caller, "minter")] pub fn mint(e: &Env, caller: Address, to: Address, token_id: u32) { Base::mint(e, &to, token_id) }
// allows either minter or burner role, does not enforce `require_auth` in the macro #[has_any_role(caller, ["minter", "burner"])] pub fn multi_role_action(e: &Env, caller: Address) -> String { caller.require_auth(); String::from_str(e, "multi_role_action_success") }
// allows either minter or burner role AND enforces `require_auth` in the macro #[only_any_role(caller, ["minter", "burner"])] pub fn multi_role_auth_action(e: &Env, caller: Address) -> String { String::from_str(e, "multi_role_auth_action_success") } }
== Benefits and Trade-offs === Benefits * Flexible role-based permission system * Hierarchical role management * Secure admin transfer process * Admin is renounceable * Easy integration with procedural macros === Trade-offs * More complex than single-owner models like Ownable == See Also * xref:utils/access/ownable.adoc[Ownable] * xref:tokens/fungible/fungible.adoc[Fungible Token] * xref:tokens/non-fungible/non-fungible.adoc[Non-Fungible Token]