Abstract
A new token standard that held the permission of an address in an ecosystem. A permission token can be transferred by the owner and can be granted/revoked to an authorized operator.
Motivation
We still need implement and manage smart contract by permissioned addresses that’s why you may see special roles like Owner, Operator… These permissions aren’t managed correctly and it’s really hard if we want to transfer, grant, revoke permissions.
Let’s check the Ownable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
_transferOwnership(_msgSender());
}
function owner() public view virtual returns (address) {
return _owner;
}
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
This contract is only able to provide basic permission for an owner, define another role or permission is expensive and it’s hard to mange the relationship between smart contracts.
Specification
This token shares the same interface with other standard:
interface PermissionToken {
function name() external returns(string memory);
function symbol() external returns(string memory);
function transfer(address to, uint256 permission) external returns(bool);
function balanceOf(address owner) external view returns(uint256)
}
Note: I don’t think permission allowance is a good idea but let’s discuss later.
Storage
A mapping can be used to store 256 bits of permissions of an address:
mapping (address => uint256) balances;
We can define a permission is a power of 2:
uint256 constant PERMISSION_NONE = 0;
uint256 constant PERMISSION_CREATE = 1;
uint256 constant PERMISSION_SIGN = 2;
uint256 constant PERMISSION_EXECUTE = 4;
To check an address have some certain permissions, we just need to do a simple trick by using bitmask check.
Checking
Define a role:
uint256 constant ROLE_ADMIN = PERMISSION_CREATE | PERMISSION_SIGN | PERMISSION_EXECUTE;
Check role and permission:
modifier onlyAllow(uint256 permission) {
require(permissionToken.balanceOf(msg.sender) & permission > 0, 'Access Denied');
_;
}
function upgradeContract() onlyAllow(ROLE_ADMIN) {
// some special code
}
Transfer permissions
Transfer method allows an owner to transfer a subset of their permissions to another address.
function transfer(address to, uint256 permissions) external returns(bool) {
require(balances[msg.sender] & permissions > 0);
require(address(to) != address(0));
// Remove permissions from sender
balances[msg.sender] = (balances[msg.sender] | permissions) ^ permissions;
// Assign permissions to receiver
balances[to] = balances[to] | permissions;
return balances[to] & permissions > 0;
}
Delegate permissions
Allow an authorized operator to trigger smart contract for behalf of permission owner.
Note: Authorized operator can not transfer delegated permissions.
Conclusion
- This approach could help reduce security fault regarding to permission management
- This proposal simplifies management process and it is extendable in the future (apparently you can define 256 permissions and 2^256 roles).
- This standard can be used to secure the interaction between two different ecosystems.
- An owner can grant a subset of their permission to a given address and they can revoke the access anytime.