Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-7303: Token 控制的 Token 流通

基于 token 所有权的访问控制方案。

Authors Ko Fujimura (@kofujimura)
Created 2023-07-09
Discussion Link https://ethereum-magicians.org/t/erc-7303-token-controlled-token-circulation/15020
Requires EIP-721, EIP-1155, EIP-5679

摘要

本 ERC 引入了一种名为 Token-Controlled Token Circulation (TCTC) 的访问控制方案。通过将与角色相关的权限表示为 ERC-721ERC-1155 token(称为 control token),可以通过铸造或销毁相应的 control token 来促进授予或撤销角色的过程。

动机

有许多方法可以为特权操作实现访问控制。一种常用的模式是 ERC-5982 中规定的“基于角色”的访问控制。然而,这种方法需要使用链下管理工具,通过其界面授予或撤销所需的角色。此外,由于许多钱包缺少显示角色授予的权限的用户界面,因此用户通常无法通过钱包了解其权限的状态。

用例

本 ERC 适用于使用 ERC-5982 中描述的基于角色的访问控制的许多场景。具体用例包括:

铸造/销毁权限: 在流通门票、优惠券、会员卡和网站访问权限等物品作为 token 的应用程序中,有必要为系统管理员提供铸造或销毁这些 token 的权限。这些权限可以在此方案中实现为 control tokens

转移权限: 在这些应用程序中的某些情况下,可能希望限制将 token 转移到特定机构的能力。在这些情况下,机构证书作为 control token 颁发。然后,此 control token 的所有权提供了调节 token 转移的方法。

地址验证: 许多应用程序需要地址验证,以防止在铸造或转移目标 token 时收件人地址出现错误。向用户颁发 control token 作为地址验证的证明,收件人在执行铸造或转移交易时需要此 control token,从而防止错误发送。在某些情况下,此用于地址验证的 control token 可能会在身份验证过程后由政府机构或特定公司颁发。

规范

本文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“NOT RECOMMENDED”、“MAY”和“OPTIONAL”应按照 RFC 2119 和 RFC 8174 中的描述进行解释。

  1. 实现 ERC-7303 标准的智能合约 MUST 将角色所需的权限表示为 ERC-721 token 或 ERC-1155 token。代表权限的 token 在此 ERC 中称为 control tokenscontrol token 可以是任何类型的 token,并且其交易可以由另一个 control token 递归控制。
  2. 为了将所需的 control token 与角色相关联,MUST 使用先前部署的 control token 合约的地址。
  3. 为了确定帐户是否具有必要的角色,SHOULD 确认 control token 的余额超过 0,并使用 ERC-721 或 ERC-1155 中定义的 balanceOf 方法。请注意,如果将 ERC-1155 token 用于 balanceOf 方法,则必须指定 typeId
  4. 要向帐户授予角色,SHOULD 使用 ERC-5679 中定义的 safeMint 方法将代表该权限的 control token 铸造到该帐户。
  5. 要从帐户撤销角色,SHOULD 使用 ERC-5679 中定义的 burn 方法销毁代表该权限的 control token
  6. 符合标准的智能合约中的角色以 bytes32 格式表示。建议将此类角色的值计算为角色名称字符串的 keccak256 哈希值,格式如下:bytes32 role = keccak256("<role_name>"),例如 bytes32 role = keccak256("MINTER")

理由

选择使用 ERC-721 或 ERC-1155 token 作为权限的控制 token 可以增强此类权限在钱包中的可见性,从而简化用户的权限管理。

通常,当将权限实现为 token 时,会使用 Soulbound Token 等规范(例如,ERC-5192)。鉴于 ERC-5192 继承自 ERC-721,因此本 ERC 选择 ERC-721 作为控制 token 的要求。

采用可转移的控制 token 可以满足需要角色委派的场景。例如,当组织内的某个权限被替换或休假时,可以将他们的权限转移给另一位成员。将控制 token 指定为可转移的决定将取决于应用程序的特定需求。

向后兼容性

本 ERC 旨在分别与 ERC-721ERC-1155ERC-5679 兼容。

参考实现

ERC-7303 提供了一个修饰符,以方便在应用程序中实现 TCTC 访问控制。 此修饰符检查帐户是否具有必要的角色。ERC-7303 还包括一个将特定角色授予指定帐户的函数。

// 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. 
     * 允许进行多次调用,在这种情况下,用户必须拥有至少一个指定的 token。
     * @param role byte32 您想要授予的角色。
     * @param contractId address 用户需要拥有的 token 的 contractId 的地址。
     */
    function _grantRoleByERC721(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. 
     * 允许进行多次调用,在这种情况下,用户必须拥有至少一个指定的 token。
     * @param role byte32 您想要授予的角色。
     * @param contractId address 用户需要拥有的 token 的 contractId 的地址。
     * @param typeId uint256 用户需要拥有的 token 类型 ID。
     */
    function _grantRoleByERC1155(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;
    }
}

以下是一个在 ERC-721 token 中利用 ERC7303 定义“minter”和“burner”角色的简单示例。拥有这些角色的帐户可以通过指定 ERC-721 或 ERC-1155 control tokens 来创建新 token 和销毁现有 token:

// 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") {
        // 指定 ERC721 control token 的已部署 contractId。
        _grantRoleByERC721(MINTER_ROLE, 0x...);
        _grantRoleByERC721(BURNER_ROLE, 0x...);

        // 指定 ERC1155 control token 的已部署 contractId 和 typeId。
        _grantRoleByERC1155(MINTER_ROLE, 0x..., ...);
        _grantRoleByERC1155(BURNER_ROLE, 0x..., ...);
    }

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

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

安全注意事项

可流通的 token 的安全性在很大程度上取决于 control tokens 的安全性。必须仔细考虑有关管理权限、铸造/转移/销毁权限以及 control tokens 合约更新可能性的设置。

特别是,使 control tokens 可转移可以实现灵活的操作,例如临时委派管理权限。但是,这也增加了流通 token 的权利可能落入不适当的第三方面前的可能性。因此,control tokens 通常应设为不可转移。如果要使 control tokens 可转移,至少应由受信任的管理员保留销毁这些 token 的权限。

版权

版权和相关权利已通过 CC0 放弃。

Citation

Please cite this document as:

Ko Fujimura (@kofujimura), "ERC-7303: Token 控制的 Token 流通 [DRAFT]," Ethereum Improvement Proposals, no. 7303, July 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7303.