Abstract
This EIP offers an alternative to Access Control Lists (ACLs) for granting authorization and enhancing security. Each permission is represented by a single bit in uint256
from which we can defined up to 256
permissions and 2²⁵⁶
roles. This approach use bitwise operator and bitmask to determine the access right which is much more efficient and flexible than string
comparison or keccak()
. We are able to specify the importance of permission based on the bit order.
Motivation
Special roles like Owner
, Operator
, Manager
, Validator
are common for many smart contracts because permissioned addresses are used to administer and manage them. It is difficult to audit and maintain these system since these permissions are not managed in a single smart contract.
Since permission and role are reflected by the permission token balance of the relevant account in the given ecosystem, cross-interactivity between many ecosystems will be made simpler.
Specification
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
Note The following specifications use syntax from Solidity 0.8.7
(or above)
Core Interface
Compliant contracts MUST implement IEIP6366Core
.
interface IEIP6366Core {
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _delegatee, uint256 _permission);
function transfer(address _to, uint256 _permission) external returns (bool success);
function approve(address _delegatee, uint256 _permission) external returns (bool success);
function permissionOf(address _owner) external view returns (uint256 permission);
function permissionRequire(uint256 _required, uint256 _permission) external view returns (bool isPermissioned);
function hasPermission(address _owner, address _actor, uint256 _required) external view returns (bool isPermissioned);
function delegated(address _owner, address _delegatee) external view returns (uint256 permission);
}
It is RECOMMENDED to define each permission as a power of 2
so that we can check for the relationship between sets of permissions using the bitwise operator and bitmask.
Example
const PERMISSION_NONE = 0;
const PERMISSION_READ = 1; // 2⁰
const PERMISSION_WRITE = 2; // 2¹
const PERMISSION_EXECUTE = 4; // 2²
// Role admin = 4 | 2 | 1 = 7
const ROLE_ADMIN = PERMISSION_READ | PERMISSION_WRITE | PERMISSION_EXECUTE;
// Role operator = 1 | 2 = 3
const ROLE_OPERATOR = PERMISSION_READ | PERMISSION_WRITE;
The most important permission SHOULD be greater than the lesser one.
transfer
Transfers a subset of _permission
permission to address _to
, and MUST emit the Transfer
event. The function SHOULD revert
if the message caller's account permission does not have the subset of the transferring permission. The function SHOULD revert
if any of transferring permission is existing on target _to
address.
Note Transfers of 0
permission MUST be treated as normal transfers and emit the Transfer
event.
function transfer(address _to, uint256 _permission) external returns (bool success);
approve
Allows _delegatee
to act for the permission owner's behalf, up to the _permission
permission, and MUST emit the Approval
event. If this function is called again it overwrites the current granted with _permission
.
Note _permission
MUST be a subset of all available permission of permission owner.
Note approve()
method SHOULD revert
if granting _permission
permission is not a subset of all available permission of permission owner.
function approve(address _delegatee, uint256 _permission) external returns (bool success);
permissionOf
Returns the account permission of the given _owner
address.
function permissionOf(address _owner) external view returns (uint256 permission);
permissionRequire
Returns true
if _required
permission is a subset of _permission
permission otherwise return false
.
function permissionRequire(uint256 _required, uint256 _permission) external view returns (bool isPermissioned);
hasPermission
Returns true
if _required
permission is a subset of _actor
's permissions or a subset of his delegated permissions granted by the _owner
.
function hasPermission(address _owner, address _actor, uint256 _required) external view returns (bool isPermissioned);
delegated
Returns the subset permission of the _owner
address were granted to _delegatee
address.
function delegated(address _owner, address _delegatee) external view returns (uint256 permission);
Transfer
MUST trigger when permission are transferred, including zero permission transfers.
A token contract which creates new tokens SHOULD emit a Transfer
event with the _from
address set to address(0x00)
when tokens are created.
event Transfer(address indexed _from, address indexed _to, uint256 _value)
Approval
MUST trigger on any successful call to approve(address _delegatee, uint256 _permission)
.
event Approval(address indexed _owner, address indexed _delegatee, uint256 _permission)
Metadata Interface
It is RECOMMENDED for compliant contracts to implement the optional extension IEIP6366Meta
.
interface IEIP6366Meta {
struct PermissionDescription {
uint256 index;
uint256 permission;
string name;
string description;
}
event UpdatePermissionDescription(uint256 indexed _permission, string indexed _name, string indexed _description);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function getDescription(uint256 _index) external view returns (PermissionDescription memory description);
function setDescription(uint256 _index, string memory _name, string memory _description) external returns (bool success);
}
name
Returns the name of the token - e.g. "OpenPermissionToken"
.
function name() external view returns (string memory);
symbol
Returns the symbol of the token. E.g. "OPT"
.
function symbol() external view returns (string memory);
getDescription
Return a description of a permission, at a given _index
.
function getDescription(uint256 _index) external view returns (PermissionDescription memory description);
setDescription
Return true
if the description was set otherwise return false
. It MUST emit UpdatePermissionDescription
event.
function setDescription(uint256 _index, string memory _name, string memory _description) external returns (bool success);
SHOULD define a relationship between index
field and permission
field in PermissionDescription
. It is RECOMMENDED to define permission
is 2 to the power of index
.
UpdatePermissionDescription
MUST trigger when description is updated.
event UpdatePermissionDescription(uint256 indexed _permission, string indexed _name, string indexed _description);
Error Interface
SHOULD NOT expected IEIP6366Error
interface was implemented.
interface IEIP6366Error {
error AccessDenied(address _owner, address _actor, uint256 _permission);
error DuplicatedPermission(uint256 _permission);
error OutOfRange();
}
Rationale
Needs discussion.
Reference Implementation
First implementation could be found here:
Security Considerations
Need more discussion.
Copyright
Copyright and related rights waived via CC0.