Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EIP: Token-Controlled Token Circulation #7303

Merged
merged 13 commits into from
Aug 8, 2023
177 changes: 177 additions & 0 deletions EIPS/eip-7303.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
---
eip: 7303
title: Token-Controlled Token Circulation
description: Access control scheme based on token ownership.
author: Ko Fujimura (@kofujimura)
discussions-to: https://ethereum-magicians.org/t/erc-7303-token-controlled-token-circulation/15020
status: Draft
type: Standards Track
category: ERC
created: 2023-07-09
requires: 721, 1155, 5679
---
## Abstract

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](./eip-721.md) or [ERC-1155](./eip-1155.md) 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](./eip-5982.md). 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](./eip-5982.md) 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.

1. Smart contracts implementing the [ERC-7303](./eip-7303.md) 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`.
2. To associate the required `control token` with the role, the address of the previously deployed contract for the `control token` MUST be used.
3. 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.
4. 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](./eip-5679.md).
5. To revoke a role from an account, the `control token` representing the privilege SHOULD be burned using the `burn` method defined in ERC-5679.
6. 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](./eip-5192.md)) 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.

## Backwards Compatibility

This ERC is designed to be compatible for [ERC-721](./eip-721), [ERC-1155](./eip-1155), and [ERC-5679](./eip-5679) respectively.
kofujimura marked this conversation as resolved.
Show resolved Hide resolved

## Reference Implementation

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.

```solidity
// SPDX-License-Identifier: Apache-2.0

pragma solidity ^0.8.9;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";

abstract contract ERC7303 {
struct ERC721Token {
address contractId;
}

struct ERC1155Token {
address contractId;
uint256 typeId;
}

mapping (bytes32 => ERC721Token[]) private _ERC721_Contracts;
mapping (bytes32 => ERC1155Token[]) private _ERC1155_Contracts;

modifier onlyHasToken(bytes32 role, address account) {
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 _grantRoleByERC721Token(bytes32 role, address contractId) 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 address The token type id that the user required to own.
*/
function _grantRoleByERC1155Token(bytes32 role, address contractId, uint256 typeId) 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(bytes32 role, address account) internal view returns (bool) {
ERC721Token[] memory ERC721Tokens = _ERC721_Contracts[role];
for (uint i = 0; i < ERC721Tokens.length; i++) {
if (IERC721(ERC721Tokens[i].contractId).balanceOf(account) > 0) return true;
}

ERC1155Token[] memory ERC1155Tokens = _ERC1155_Contracts[role];
for (uint i = 0; i < ERC1155Tokens.length; i++) {
if (IERC1155(ERC1155Tokens[i].contractId).balanceOf(account, ERC1155Tokens[i].typeId) > 0) return true;
}

return false;
}
}
```

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:

```solidity
// SPDX-License-Identifier: Apache-2.0

pragma solidity ^0.8.9;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "./ERC7303.sol";

contract MyToken is ERC721, ERC7303 {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");

constructor() ERC721("MyToken", "MTK") {
// Specifies the deployed contractId of ERC721 control token.
_grantRoleByERC721Token(MINTER_ROLE, 0x...);
_grantRoleByERC721Token(BURNER_ROLE, 0x...);

// Specifies the deployed contractId and typeId of ERC1155 control token.
_grantRoleByERC1155Token(MINTER_ROLE, 0x..., ...);
_grantRoleByERC1155Token(BURNER_ROLE, 0x..., ...);
}

function safeMint(address to, uint256 tokenId, string memory uri)
public onlyHasToken(MINTER_ROLE, msg.sender)
{
_safeMint(to, tokenId);
}

function burn(uint256 tokenId)
public onlyHasToken(BURNER_ROLE, msg.sender)
{
_burn(tokenId);
}
}
```

## Security Considerations

Needs discussion.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).