ERC-6366 Permission token standard (Permission Token)
Review · Signatures & auth · created 2022-01-19
A standard that stores the permissions held by an address as bits within a single uint256 number, and allows them to be transferred or delegated like a token. Permission checks are handled quickly using bitwise operations.
Why it is needed
Smart contracts commonly have special roles such as Owner, Operator, Manager, and Validator, and addresses holding these permissions manage the contract. However, when such permissions are managed separately in each contract, they cannot be viewed in one place, making them hard to audit and hard to maintain. In addition, the existing Access Control List (ACL) approach checks permissions by comparing strings or keccak256 hashes, which is costly and inflexible. This standard proposes a much more efficient and flexible alternative by representing permissions as bits within a single uint256 and checking them with bitwise operators and bitmasks. Since permissions and roles are expressed through the permission token balance an account holds within the given ecosystem, interoperability across multiple ecosystems also becomes simpler.
What to implement
event Transfer(address indexed _from, address indexed _to, uint256 indexed _permission);: An event that must fire whenever a permission is transferred. It also fires when transferring a zero permission.event Approval(address indexed _owner, address indexed _delegatee, uint256 indexed _permission);: An event that must fire whenever an approve call succeeds.function transfer(address _to, uint256 _permission) external returns (bool success);: Transfers the permission subset _permission to address _to. This must revert if the caller does not hold that permission, or if the recipient already holds it.function approve(address _delegatee, uint256 _permission) external returns (bool success);: Allows delegatee _delegatee to act on behalf of the permission holder within the scope of _permission. Calling this again overwrites the existing allowance.function permissionOf(address _owner) external view returns (uint256 permission);: Returns the set of permissions held by address _owner.function permissionRequire(uint256 _permission, uint256 _required) external view returns (bool isPermissioned);: Returns true if _required is a subset of _permission, false otherwise.function hasPermission(address _owner, address _actor, uint256 _required) external view returns (bool isPermissioned);: Returns true if _required is a subset of _actor's own permissions or of the permissions delegated by _owner.function delegated(address _owner, address _delegatee) external view returns (uint256 permission);: Returns the subset of permissions _owner has delegated to _delegatee.error AccessDenied(address _owner, address _actor, uint256 _permission);: An error used when the holder or delegatee does not have the required permission.error DuplicatedPermission(uint256 _permission);: An error used when there is a conflict between permission sets.error OutOfRange();: An error used when data is out of the allowed range.
In depth
Representing permissions as uint256 bits
A single address's entire permission set is stored in one uint256 value. As described in ERC-6617, each permission corresponds to one bit of the uint256. Whether a permission is included is determined using bitwise operators and bitmasks, and the original text explains that this approach is far more efficient and flexible than string or keccak256 comparison. Defining each permission as a power of two is RECOMMENDED, since that allows ERC-6617 to check subset relationships between permission sets. Compliant contracts MUST implement IEIP6366Core, and this standard requires ERC-6617 as a prerequisite.
Transfer behavior rules
transfer(_to, _permission) transfers the permission subset _permission to _to. It SHOULD revert if the caller's account does not hold the subset of permissions being transferred. It SHOULD also revert if any of the permissions being transferred already exist on the target address _to. The Transfer event MUST fire whenever a permission is transferred, including when transferring a zero permission.
Delegation via approve and delegated
approve(_delegatee, _permission) allows a delegatee to act on behalf of the permission holder within the scope of _permission. Calling the same function again overwrites the currently approved value with the new _permission. It SHOULD revert if the _permission being granted is not a subset of the holder's full permission set. Every successful call MUST fire an Approval event. delegated(_owner, _delegatee) returns the subset of permissions the holder has granted to the delegatee.
Three query functions
permissionOf(_owner) returns the permission set of the given address. permissionRequire(_permission, _required) is a pure set-comparison function that returns true if _required is a subset of _permission. hasPermission(_owner, _actor, _required) returns true if _required is a subset of _actor's own permissions, or a subset of the permissions delegated by _owner. In other words, it checks the actor's own permissions and delegated permissions in a single call.
Metadata and error interfaces
Compliant contracts are RECOMMENDED to implement the optional extension IEIP6617Meta. Descriptions SHOULD be defined for base permissions and major combinations, but descriptions SHOULD NOT be defined for every possible sub-combination. Compatible tokens MAY also implement IEIP6366Error, which defines three errors: AccessDenied when a required permission is missing, DuplicatedPermission when permission sets conflict, and OutOfRange when data is out of range.
A proposal still under review
This proposal's status is Review, meaning its contents may still change. The original text's Rationale section only states "Needs discussion," and the Security Considerations section only states "Need more discussion," so the design rationale and security considerations have not yet been filled in. An ERC-6366 Core implementation file is provided as the reference implementation.