This ERC introduces an access control scheme termed Token-Controlled Token Circulation (TCTC). By representing the privileges associated with a role as an ERC-721 or ERC-1155 token (referred to as a control token), the processes of granting or revoking a role can be facilitated through the minting or burning of the corresponding control token.
Motivation
There are numerous methods to implement access control for privileged actions. A commonly utilized pattern is “role-based” access control as specified in ERC-5982. This method, however, necessitates the use of an off-chain management tool to grant or revoke required roles through its interface. Additionally, as many wallets lack a user interface that displays the privileges granted by a role, users are often unable to comprehend the status of their privileges through the wallet.
Use Cases
This ERC is applicable in many scenarios where role-based access control as described in ERC-5982 is used. Specific use cases include:
Mint/Burn Permission:
In applications that circulate items such as tickets, coupons, membership cards, and site access rights as tokens, it is necessary to provide the system administrator with the authority to mint or burn these tokens. These permissions can be realized as control tokens in this scheme.
Transfer Permission:
In some situations within these applications, it may be desirable to limit the ability to transfer tokens to specific agencies. In these cases, an agency certificate is issued as a control token. The ownership of this control token then provides the means to regulate token transfers.
Address Verification:
Many applications require address verification to prevent errors in the recipient’s address when minting or transferring target tokens. A control token is issued as proof of address verification to users, which is required by the recipient when a mint or transfer transaction is executed, thus preventing misdeliveries. In some instances, this control token for address verification may be issued by a government agency or specific company after an identity verification process.
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.
Smart contracts implementing the ERC-7303 standard MUST represent the privilege required by the role as an ERC-721 token or ERC-1155 token. The tokens that represent privileges are called control tokens in this ERC. The control token can be any type of token, and its transactions may be recursively controlled by another control token.
To associate the required control token with the role, the address of the previously deployed contract for the control token MUST be used.
To ascertain whether an account possesses the necessary role, it SHOULD be confirmed that the balance of the control token exceeds 0, utilizing the balanceOf method defined in ERC-721 or ERC-1155. Note that the typeId must be specified if an ERC-1155 token is used for the balanceOf method.
To grant a role to an account, a control token representing the privilege SHOULD be minted to the account using safeMint method defined in ERC-5679.
To revoke a role from an account, the control token representing the privilege SHOULD be burned using the burn method defined in ERC-5679.
A role in a compliant smart contract is represented in the format of bytes32. It’s RECOMMENDED the value of such role is computed as a keccak256 hash of a string of the role name, in this format: bytes32 role = keccak256("<role_name>") such as bytes32 role = keccak256("MINTER").
Rationale
The choice to utilize ERC-721 or ERC-1155 token as the control token for privileges enhances visibility of such privileges within wallets, thus simplifying privilege management for users.
Generally, when realizing privileges as tokens, specifications like Soulbound Token (e.g., ERC-5192) are used. Given that ERC-5192 inherits from ERC-721, this ERC has choiced ERC-721 as the requirement for the control token.
Employing a transferable control token can cater to scenarios where role delegation is necessary. For example, when an authority within an organization is replaced or on vacation, the ability to transfer their privileges to another member becomes possible. The decision to designate the control token as transferable will depend on the specific needs of the application.
ERC-7303 provides a modifier to facilitate the implementation of TCTC access control in applications. This modifier checks if an account possesses the necessary role. ERC-7303 also includes a function that grants a specific role to a designated account.
// SPDX-License-Identifier: Apache-2.0
pragmasolidity^0.8.9;import"@openzeppelin/contracts/token/ERC721/ERC721.sol";import"@openzeppelin/contracts/token/ERC1155/ERC1155.sol";abstractcontractERC7303{structERC721Token{addresscontractId;}structERC1155Token{addresscontractId;uint256typeId;}mapping(bytes32=>ERC721Token[])private_ERC721_Contracts;mapping(bytes32=>ERC1155Token[])private_ERC1155_Contracts;modifieronlyHasToken(bytes32role,addressaccount){require(_checkHasToken(role,account),"ERC7303: not has a required token");_;}/**
* @notice Grant a role to user who owns a control token specified by the ERC-721 contractId.
* Multiple calls are allowed, in this case the user must own at least one of the specified token.
* @param role byte32 The role which you want to grant.
* @param contractId address The address of contractId of which token the user required to own.
*/function_grantRoleByERC721(bytes32role,addresscontractId)internal{require(IERC165(contractId).supportsInterface(type(IERC721).interfaceId),"ERC7303: provided contract does not support ERC721 interface");_ERC721_Contracts[role].push(ERC721Token(contractId));}/**
* @notice Grant a role to user who owns a control token specified by the ERC-1155 contractId.
* Multiple calls are allowed, in this case the user must own at least one of the specified token.
* @param role byte32 The role which you want to grant.
* @param contractId address The address of contractId of which token the user required to own.
* @param typeId uint256 The token type id that the user required to own.
*/function_grantRoleByERC1155(bytes32role,addresscontractId,uint256typeId)internal{require(IERC165(contractId).supportsInterface(type(IERC1155).interfaceId),"ERC7303: provided contract does not support ERC1155 interface");_ERC1155_Contracts[role].push(ERC1155Token(contractId,typeId));}function_checkHasToken(bytes32role,addressaccount)internalviewreturns(bool){ERC721Token[]memoryERC721Tokens=_ERC721_Contracts[role];for(uinti=0;i<ERC721Tokens.length;i++){if(IERC721(ERC721Tokens[i].contractId).balanceOf(account)>0)returntrue;}ERC1155Token[]memoryERC1155Tokens=_ERC1155_Contracts[role];for(uinti=0;i<ERC1155Tokens.length;i++){if(IERC1155(ERC1155Tokens[i].contractId).balanceOf(account,ERC1155Tokens[i].typeId)>0)returntrue;}returnfalse;}}
The following is a simple example of utilizing ERC7303 within an ERC-721 token to define “minter” and “burner” roles. Accounts possessing these roles are allowed to create new tokens and destroy existing tokens, facilitated by specifying ERC-721 or ERC-1155 control tokens:
// SPDX-License-Identifier: Apache-2.0
pragmasolidity^0.8.9;import"@openzeppelin/contracts/token/ERC721/ERC721.sol";import"@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";import"./ERC7303.sol";contractMyTokenisERC721,ERC7303{bytes32publicconstantMINTER_ROLE=keccak256("MINTER_ROLE");bytes32publicconstantBURNER_ROLE=keccak256("BURNER_ROLE");constructor()ERC721("MyToken","MTK"){// Specifies the deployed contractId of ERC721 control token.
_grantRoleByERC721(MINTER_ROLE,0x...);_grantRoleByERC721(BURNER_ROLE,0x...);// Specifies the deployed contractId and typeId of ERC1155 control token.
_grantRoleByERC1155(MINTER_ROLE,0x...,...);_grantRoleByERC1155(BURNER_ROLE,0x...,...);}functionsafeMint(addressto,uint256tokenId,stringmemoryuri)publiconlyHasToken(MINTER_ROLE,msg.sender){_safeMint(to,tokenId);}functionburn(uint256tokenId)publiconlyHasToken(BURNER_ROLE,msg.sender){_burn(tokenId);}}