Module access_control

Module access_control 

Source
Expand description

Access control module for Soroban contracts

This module provides functionality to manage role-based access control in Soroban contracts.

§Usage

There is a single overarching admin, and the admin has enough privileges to call any function given in the AccessControl trait.

This admin must be set in the constructor of the contract. Else, none of the methods exposed by this module will work. You can follow the nft-access-control example.

§Admin Transfers

Transferring the top-level admin is a critical action, and as such, it is implemented as a two-step process to prevent accidental or malicious takeovers:

  1. The current admin initiates the transfer by specifying the new_admin and a live_until_ledger, which defines the expiration time for the offer.
  2. The designated new_admin must explicitly accept the transfer to complete it.

Until the transfer is accepted, the original admin retains full control, and the transfer can be overridden or canceled by initiating a new one or using a live_until_ledger of 0.

This handshake mechanism ensures that the recipient is aware and willing to assume responsibility, providing a robust safeguard in governance-sensitive deployments.

§Role Hierarchy

Each role can have an “admin role” specified for it. For example, if you create two roles: minter and minter_admin, you can assign minter_admin as the admin role for the minter role. This will allow to accounts with minter_admin role to grant/revoke the minter role to other accounts.

One can create up to 256 roles simultaneously, and create a chain of command structure if they want to go with this approach.

If you need even more granular control over which roles can do what, you can introduce your own business logic, and annotate it with our macro:

#[has_role(caller, "minter_admin")]
pub fn custom_sensitive_logic(e: &Env, caller: Address) {
    ...
}

§⚠️ Warning: Circular Admin Relationships

When designing your role hierarchy, be careful to avoid creating circular admin relationships. For example, it’s possible but not recommended to assign MINT_ADMIN as the admin of MINT_ROLE while also making MINT_ROLE the admin of MINT_ADMIN. Such circular relationships can lead to unintended consequences, including:

  • Race conditions where each role can revoke the other
  • Potential security vulnerabilities in role management
  • Confusing governance structures that are difficult to reason about

§Enumeration of Roles

In this access control system, roles don’t exist as standalone entities. Instead, the system stores account-role pairs in storage with additional enumeration logic:

  • When a role is granted to an account, the account-role pair is stored and added to enumeration storage (RoleAccountsCount and RoleAccounts).
  • When a role is revoked from an account, the account-role pair is removed from storage and from enumeration.
  • If all accounts are removed from a role, the helper storage items for that role become empty or 0, but the entries themselves remain.

This means that the question of whether a role can “exist” with 0 accounts is technically invalid, because roles only exist through their relationships with accounts. When checking if a role has any accounts via get_role_member_count, it returns 0 in two cases:

  1. When accounts were assigned to a role but later all were removed.
  2. When a role never existed in the first place.

Structs§

AccessControlArgs
AccessControlArgs is a type for building arg lists for functions defined in “AccessControl”.
AccessControlClient
AccessControlClient is a client for calling the contract defined in “AccessControl”.
AccessControlSpec
AdminRenounced
Event emitted when the admin role is renounced.
AdminTransferCompleted
Event emitted when an admin transfer is completed.
AdminTransferInitiated
Event emitted when an admin transfer is initiated.
RoleAdminChanged
Event emitted when a role admin is changed.
RoleGranted
Event emitted when a role is granted.
RoleRevoked
Event emitted when a role is revoked.

Enums§

AccessControlError
AccessControlStorageKey
Storage keys for the data associated with the access control

Constants§

MAX_ROLES
Maximum number of roles that can exist simultaneously.
ROLE_EXTEND_AMOUNT
ROLE_TTL_THRESHOLD

Statics§

__SPEC_XDR_EVENT_ADMINRENOUNCED
__SPEC_XDR_EVENT_ADMINTRANSFERCOMPLETED
__SPEC_XDR_EVENT_ADMINTRANSFERINITIATED
__SPEC_XDR_EVENT_ROLEADMINCHANGED
__SPEC_XDR_EVENT_ROLEGRANTED
__SPEC_XDR_EVENT_ROLEREVOKED
__SPEC_XDR_TYPE_ACCESSCONTROLERROR

Traits§

AccessControl

Functions§

accept_admin_transfer
Completes the 2-step admin transfer.
add_to_role_enumeration
Adds an account to role enumeration.
emit_admin_renounced
Emits an event when the admin role is renounced.
emit_admin_transfer_completed
Emits an event when an admin transfer is completed.
emit_admin_transfer_initiated
Emits an event when an admin transfer is initiated.
emit_role_admin_changed
Emits an event when the admin role for a role changes.
emit_role_granted
Emits an event when a role is granted to an account.
emit_role_revoked
Emits an event when a role is revoked from an account.
enforce_admin_auth
Retrieves the admin from storage, enforces authorization, and returns the admin address.
ensure_if_admin_or_admin_role
Ensures that the caller is either the contract admin or has the admin role for the specified role.
ensure_role
Ensures that the caller has the specified role. This function is used to check if an account has a specific role. The main purpose of this function is to act as a helper for the #[has_role] macro.
get_admin
Returns the admin account.
get_existing_roles
Returns a vector containing all existing roles. Defaults to empty vector if no roles exist.
get_role_admin
Returns the admin role for a specific role. If no admin role is explicitly set, returns None.
get_role_member
Returns the account at the specified index for a given role.
get_role_member_count
Returns the total number of accounts that have the specified role. If the role does not exist, it returns 0.
grant_role
Grants a role to an account. Creates the role if it does not exist. Returns early if the account already has the role.
grant_role_no_auth
Low-level function to grant a role to an account without performing authorization checks. Creates the role if it does not exist. Returns early if the account already has the role.
has_role
Returns Some(index) if the account has the specified role, where index is the position of the account for that role, and can be used to query get_role_member. Returns None if the account does not have the specified role.
remove_from_role_enumeration
Removes an account from role enumeration.
remove_role_accounts_count_no_auth
Removes the role accounts count for a specified role without performing authorization checks.
remove_role_admin_no_auth
Removes the admin role for a specified role without performing authorization checks.
renounce_admin
Allows the current admin to renounce their role, making the contract permanently admin-less. This is useful for decentralization purposes or when the admin role is no longer needed. Once the admin is renounced, it cannot be reinstated.
renounce_role
Allows an account to renounce a role assigned to itself. Users can only renounce roles for their own account.
revoke_role
Revokes a role from an account.
revoke_role_no_auth
Low-level function to revoke a role from an account without performing authorization checks.
set_admin
Sets the overarching admin role.
set_role_admin
Sets admin_role as the admin role for role. The admin role for a role controls who can grant and revoke that role.
set_role_admin_no_auth
Low-level function to set the admin role for a specified role without performing authorization checks.
transfer_admin_role
Initiates admin role transfer. Admin privileges for the current admin are not revoked until the recipient accepts the transfer. Overrides the previous pending transfer if there is one.