ERC-5982 Role-based access control standard (Role-based Access Control)
Review · Other · created 2022-11-15
Defines a common interface for querying, granting, creating, and deleting smart contract permissions by role. A role is represented as a bytes32 value, and its power is defined as the ability to call specific functions.
Why it is needed
Smart contracts have privileged functions, such as minting tokens or changing settings, that should not be callable by just anyone. The simplest way to protect these is ownership-based access control, which concentrates all permissions in a single owner account. Role-based access control, by contrast, assigns one or more roles to multiple users and grants access to privileged functions based on role. Following the principle of least privilege, necessary permissions can be split among multiple people, making this approach safer and more flexible than ownership-based control. However, when each contract implements role management differently, it becomes hard for wallets or tools to read the permission structure in a consistent way. This standard defines a single common interface for reading, granting, creating, and removing roles, and for expressing role metadata.
What to implement
function hasRole(bytes32 role, address account) external view returns (bool);: Checks whether an account holds the given role.function grantRole(bytes32 role, address account) external;: Grants a role to an account.function revokeRole(bytes32 role, address account) external;: Revokes a role from an account.event RoleGranted(address indexed grantor, bytes32 indexed role, address indexed grantee, bytes _data);: Records who granted which role to whom.event RoleRevoked(address indexed revoker, bytes32 indexed role, address indexed revokee, bytes _data);: Records who revoked which role from whom.event RoleCreated(address indexed roleGrantor, bytes32 role, bytes32 adminOfRole, string name, string desc, string uri, bytes32 calldata _data);: Records that a new role was created, together with its admin role, name, description, and URI.event RoleDestroyed(address indexed roleDestroyer, bytes32 role, bytes32 calldata _data);: Records that a role was deleted.event RolePowerSet(address indexed rolePowerSetter, bytes32 role, bytes4 methods, bytes calldata _data);: Records that the method permissions a role can call were set.function grantRole(bytes32 role, address account, bytes calldata _data) external;: Grants a role to an account, passing along arbitrary data _data.function revokeRole(bytes32 role, address account, bytes calldata _data) external;: Revokes a role from an account, passing along arbitrary data _data.function createRole(bytes32 role, bytes32 adminOfRole, string name, string desc, string uri, bytes32 calldata _data) external;: Creates a new role and registers the role that administers it, along with its name, description, and URI.function destroyRole(bytes32 role, bytes32 calldata _data) external;: Deletes a role.function setRolePower(bytes32 role, bytes4 methods, bytes calldata _data) view external returns(bool);: Specifies, via a bytes4 selector, the method a role is allowed to call.function hasRole(bytes32 role, address account, bytes calldata _data) external view returns (bool);: Checks whether an account holds a role, passing along arbitrary data _data.function canGrantRole(bytes32 grantor, bytes32 grantee, bytes calldata _data) view external returns(bool);: Checks whether one role (grantor) can grant another role (grantee).function canRevokeRole(bytes32 revoker, bytes32 revokee, address account, bytes calldata _data) view external returns(bool);: Checks which role can revoke a specific account's role.function canExecute(bytes32 executor, bytes4 methods, bytes32 calldata payload, bytes calldata _data) view external returns(bool);: Checks which role is allowed to execute a specific method.function roleName(bytes32) external view returns(string);: Returns the name of the role.function roleDescription(bytes32) external view returns(string);: Returns the description of the role.function roleURI(bytes32) external view returns(string);: Returns the URI associated with the role.
In depth
Representing roles and the creation rule
A role is represented as a single bytes32 value. It is RECOMMENDED that the value be computed as the keccak256 hash of the role name, in the form bytes32 role = keccak256("<role_name>"). For example, a minter role is created as bytes32 role = keccak256("MINTER"). Because the role value is computed directly from a human-readable role name, anyone who knows the name can derive the same role value.
The required IERC_ACL_CORE interface
Contracts following the standard MUST implement IERC_ACL_CORE. This interface consists of three functions. hasRole(bytes32 role, address account) checks whether an account holds a role, grantRole(bytes32 role, address account) grants a role, and revokeRole(bytes32 role, address account) revokes a role. The function names and parameters were chosen to be backward compatible with the OpenZeppelin implementation. Also, a separate renounceRole function for self-renouncing a role was not adopted; it was folded into revokeRole to keep the interface simple.
Recommended extension IERC_ACL_GENERAL
IERC_ACL_GENERAL is a RECOMMENDED optional extension. It provides extended versions of grantRole, revokeRole, and hasRole that also pass arbitrary data _data, along with createRole and destroyRole for creating and removing roles, and setRolePower for setting a role's method permissions. createRole takes the role's admin role (adminOfRole), name, description (desc), and URI together. View functions such as canGrantRole, canRevokeRole, and canExecute let you check in advance which roles can grant roles, revoke roles, or execute methods. State changes are recorded via the RoleGranted, RoleRevoked, RoleCreated, RoleDestroyed, and RolePowerSet events. The _data parameter of these functions follows ERC-5750, allowing for future extensions.
A role's power is its ability to call methods
This standard defines a role's power as the ability to call a specific method identified by a bytes4 method selector. A role is no longer just an abstract title but is made concrete by which functions it can call. Calling setRolePower(bytes32 role, bytes4 methods, bytes calldata _data) to set a role's method permission emits a RolePowerSet event, and canExecute(bytes32 executor, bytes4 methods, bytes32 calldata payload, bytes calldata _data) lets you check in advance whether a given role can execute a given method.
Role metadata: IERC_ACL_METADATA
IERC_ACL_METADATA is an optional (MAY) extension. roleName(bytes32) returns a role's name, roleDescription(bytes32) returns its description, and roleURI(bytes32) returns a URI associated with the role. Since a role itself is a bytes32 value and hard for humans to read, implementing this interface lets wallets and tools display the role's meaning in a human-readable form. createRole in IERC_ACL_GENERAL also takes a name, desc, and uri when a role is created, and records them in the RoleCreated event.
ERC-165 support and proposal status
Compliant contracts SHOULD implement the ERC-165 identifier. This proposal requires ERC-165 and ERC-5750. Meanwhile, the original's Backwards Compatibility and Security Considerations sections are both still marked as needing discussion and have not yet been filled in. This proposal is currently in Review status, is not a finalized standard, and its content may still change.