In this issue, we would like to discuss adding role-based access control (RBAC) to miden-standards. Instead of having a network fungible faucet where all access control is managed by an owner as in the Ownable2Step , RBAC will allow additional roles to be defined.
For example, access control for procedures such as mint, burn, and the pause procedure that we plan to add in the future can be delegated to separate roles. In practice, this means introducing and managing roles such as MINTER, BURNER, and PAUSER and their corresponding admins such as MINTER_ADMIN , BURNER_ADMIN , and PAUSER_ADMIN
The management of these roles will itself be controlled by an Admin, which behaves similarly to the owner in Ownable2Step.
role_symbol :
- 12 field uppercase character with underscore fits in one field element similarly to
TokenSymbol
- example:
"MINTER"
role_admin_symbol :
- 12 field uppercase character with underscore fits in one field element similarly to
TokenSymbol
- example:
"MINTER_ADMIN"
Related Issue: #2683
Storage Layout:
# Stores the current root admin and the nominated admin using the same
# 2-step transfer pattern as Ownable2Step.
# map/slot entries:
# [admin_suffix, admin_prefix, nominated_admin_suffix, nominated_admin_prefix]
const ADMIN_CONFIG_SLOT = word("miden::standards::access::role_based_access_control::admin_config")
# Stores global RBAC state. `active_role_count` is needed because
# ACTIVE_ROLES_SLOT is a map, do not expose length.
# map/slot entries:
# [active_role_count, 0, 0, 0]
const RBAC_STATE_SLOT = word("miden::standards::access::role_based_access_control::state")
# The list of roles that currently have at least one member.
# This slot is only for active-role enumeration.
# Map entries:
# [0, 0, 0, active_role_index] -> [role_symbol, 0, 0, 0]
const ACTIVE_ROLES_SLOT = word("miden::standards::access::role_based_access_control::active_roles")
# Stores per-role data.
# - `member_count` is the number of accounts currently holding the role.
# - `admin_role_symbol = 0` means the role is managed only by the root admin.
# - `active_role_index_plus_one = 0` means the role is not currently in ACTIVE_ROLES_SLOT.
# Otherwise it stores `active_role_index + 1`, so zero can remain.
# - `exists_flag = 1` means the role is defined, otherwise 0.
# map/slot entries:
# [0, 0, 0, role_symbol] -> [member_count, admin_role_symbol, active_role_index_plus_one, exists_flag]
const ROLE_CONFIGS_SLOT = word("miden::standards::access::role_based_access_control::role_config")
# Member map for each role, and is used for member enumeration.
# Map entries:
# [0, 0, role_symbol, member_index] -> [account_suffix, account_prefix, 0, 0]
const ROLE_MEMBERS_SLOT = word("miden::standards::access::role_based_access_control::role_members")
# Map entries
# [0, role_symbol, account_suffix, account_prefix] -> [member_index_plus_one, 0, 0, 0]
const ROLE_MEMBER_INDEX_SLOT = word("miden::standards::access::role_based_access_control::role_member_index")
Procedures:
Helper Procedures:
#! Returns whether a role is defined.
#!
#! Inputs: [role_symbol]
#! Outputs: [is_defined]
#! - 1 if the role exists, otherwise 0.
proc is_role_defined
...
end
#! Returns the config word for a role.
#!
#! Inputs: [role_symbol]
#! Outputs: [member_count, admin_role_symbol, active_role_index_plus_one, exists_flag]
proc get_role_config
...
end
#! Writes the config word for a role.
#!
#! Inputs: [role_symbol, member_count, admin_role_symbol, active_role_index_plus_one, exists_flag]
#! Outputs: []
proc set_role_config
...
end
#! Returns whether the sender is the current root admin.
#!
#! Inputs: []
#! Outputs: [is_admin]
proc is_sender_admin
...
end
#! Returns whether the sender holds the given role.
#!
#! Inputs: [role_symbol]
#! Outputs: [has_role]
#! - 1 if the sender holds the role, otherwise 0.
proc is_sender_in_role
...
end
#! Enforces that the sender is either the root admin or holds the admin role of `role_symbol`.
#!
#! Inputs: [role_symbol]
#! Outputs: []
#!
#! Panics:
#! - If the sender is neither the root admin nor the delegated admin role holder.
proc assert_sender_is_admin_or_role_admin
...
end
#! Adds a role to ACTIVE_ROLES_SLOT if it is transitioning from zero members to one member.
#!
#! Inputs: [role_symbol]
#! Outputs: []
#!
#! Side effects:
#! - Updates RBAC_STATE_SLOT
#! - Writes ACTIVE_ROLES_SLOT
#! - Updates ROLE_CONFIGS_SLOT.active_role_index_plus_one
proc add_role_to_active_set
...
end
#! Removes a role from ACTIVE_ROLES_SLOT if it is transitioning from one member to zero members.
#!
#! Inputs: [role_symbol]
#! Outputs: []
#!
#! Side effects:
#! - Updates RBAC_STATE_SLOT
#! - Writes ACTIVE_ROLES_SLOT
#! - Updates ROLE_CONFIGS_SLOT.active_role_index_plus_one
proc remove_role_from_active_set
...
end
#! Grants a role internally.
#!
#! Inputs: [role_symbol, account_suffix, account_prefix]
#! Outputs: []
#!
#! Notes:
#! - Defines the role if it does not exist yet.
#! - Adds the role to ACTIVE_ROLES_SLOT if member_count changes from 0 to 1.
proc grant_role_internal
...
end
#! Revokes a role internally.
#!
#! Inputs: [role_symbol, account_suffix, account_prefix]
#! Outputs: []
#!
#! Panics:
#! - If the account does not hold the role.
#!
#! Notes:
#! - Removes the role from ACTIVE_ROLES_SLOT if member_count changes from 1 to 0.
proc revoke_role_internal
...
end
Public Interface:
#! Checks that the sender is the current root admin.
#!
#! Inputs: []
#! Outputs: []
#!
#! Panics:
#! - If the sender is not the current root admin.
pub proc assert_sender_is_admin
...
end
#! Checks that the sender holds the given role.
#!
#! Inputs: [role_symbol]
#! Outputs: []
#!
#! Panics:
#! - If the sender does not hold the role.
pub proc assert_sender_has_role
...
end
#! Returns the current root admin.
#!
#! Inputs: []
#! Outputs: [admin_suffix, admin_prefix]
pub proc get_admin
...
end
#! Returns the nominated admin.
#!
#! Inputs: []
#! Outputs: [nominated_admin_suffix, nominated_admin_prefix]
#!
#! Notes:
#! - Returns `[0, 0]` if no admin transfer is in progress.
pub proc get_nominated_admin
...
end
#! Initiates a 2-step admin transfer by setting the nominated admin.
#!
#! Inputs: [new_admin_suffix, new_admin_prefix]
#! Outputs: []
#!
#! Panics:
#! - If the sender is not the current root admin.
pub proc transfer_admin
...
end
#! Accepts the pending admin transfer.
#!
#! Inputs: []
#! Outputs: []
#!
#! Panics:
#! - If the sender is not the nominated admin.
#! - If no nominated admin exists.
pub proc accept_admin
...
end
#! Renounces the root admin role permanently.
#!
#! Inputs: []
#! Outputs: []
#!
#! Panics:
#! - If the sender is not the current root admin.
#! - If an admin transfer is currently in progress.
pub proc renounce_admin
...
end
#! Returns whether a role is defined.
#!
#! Inputs: [role_symbol]
#! Outputs: [is_defined]
#! - 1 if the role exists, otherwise 0.
pub proc role_exists
...
end
#! Returns the delegated admin role for a role.
#!
#! Inputs: [role_symbol]
#! Outputs: [admin_role_symbol]
#!
#! Notes:
#! - Returns `0` if the role has no delegated admin role and is managed only by the root admin.
pub proc get_role_admin
...
end
#! Returns how many accounts currently hold a role.
#!
#! Inputs: [role_symbol]
#! Outputs: [member_count]
#!
#! Notes:
#! - Returns `0` for an undefined role or an empty role.
pub proc get_role_member_count
...
end
#! Returns whether an account holds a role.
#!
#! Inputs: [role_symbol, account_suffix, account_prefix]
#! Outputs: [has_role]
#! - 1 if the account holds the role, otherwise 0.
pub proc has_role
...
end
#! Returns the account at a given member index for a role.
#!
#! Inputs: [role_symbol, member_index]
#! Outputs: [account_suffix, account_prefix]
#!
#! Panics:
#! - If `member_index` is out of bounds.
pub proc get_role_member
...
end
#! Returns the number of currently active roles.
#!
#! Inputs: []
#! Outputs: [active_role_count]
pub proc get_active_role_count
...
end
#! Returns the role symbol at a given active-role index.
#!
#! Inputs: [active_role_index]
#! Outputs: [role_symbol]
#!
#! Panics:
#! - If `active_role_index` is out of bounds.
pub proc get_active_role
...
end
#! Sets or updates the delegated admin role for a role.
#!
#! Inputs: [role_symbol, admin_role_symbol]
#! Outputs: []
#!
#! Panics:
#! - If the sender is not the current root admin.
#!
#! Notes:
#! - Defines the role if it does not exist yet.
#! - `admin_role_symbol = 0` means only the root admin can manage `role_symbol`.
pub proc set_role_admin
...
end
#! Grants a role to an account.
#!
#! Inputs: [role_symbol, account_suffix, account_prefix]
#! Outputs: []
#!
#! Panics:
#! - If the sender is neither the current root admin nor a holder of the role's delegated admin role.
#!
#! Notes:
#! - Defines the role if it does not exist yet.
#! - Adds the role to ACTIVE_ROLES_SLOT if member_count changes from 0 to 1.
pub proc grant_role
...
end
#! Revokes a role from an account.
#!
#! Inputs: [role_symbol, account_suffix, account_prefix]
#! Outputs: []
#!
#! Panics:
#! - If the sender is neither the current root admin nor a holder of the role's delegated admin role.
#! - If the account does not hold the role.
#!
#! Notes:
#! - Removes the role from ACTIVE_ROLES_SLOT if member_count changes from 1 to 0.
pub proc revoke_role
...
end
#! Allows the sender to renounce a role held by the sender.
#!
#! Inputs: [role_symbol]
#! Outputs: []
#!
#! Panics:
#! - If the sender does not hold the role.
pub proc renounce_role
...
end
After this issue:
- Integrate this into current MintPolicy and upcoming BurnPolicy:
In this issue, we would like to discuss adding role-based access control (RBAC) to
miden-standards. Instead of having a network fungible faucet where all access control is managed by an owner as in theOwnable2Step, RBAC will allow additional roles to be defined.For example, access control for procedures such as
mint,burn, and thepauseprocedure that we plan to add in the future can be delegated to separate roles. In practice, this means introducing and managing roles such asMINTER,BURNER, andPAUSERand their corresponding admins such asMINTER_ADMIN,BURNER_ADMIN, andPAUSER_ADMINThe management of these roles will itself be controlled by an Admin, which behaves similarly to the owner in
Ownable2Step.role_symbol:TokenSymbol"MINTER"role_admin_symbol:TokenSymbol"MINTER_ADMIN"Related Issue: #2683
Storage Layout:
Procedures:
After this issue: