Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-7508: 动态链上代币属性存储库

在公共利益存储库中动态链上存储代币属性。

Authors Steven Pineda (@steven2308), Jan Turk (@ThunderDeliverer)
Created 2023-08-15
Discussion Link https://ethereum-magicians.org/t/dynamic-on-chain-token-attributes-repository/15667
Requires EIP-165

摘要

公共链上非同质化代币属性存储库标准为兼容 ERC-721ERC-1155 的代币提供了将其属性存储在链上的能力,任何与它们交互的外部智能合约都可以访问这些属性。

本提案引入了在公共非门控存储库智能合约中为 NFT 分配属性的能力,该合约在所有网络中都可以通过相同的地址访问。该存储库智能合约被设计为一个公共利益存储库,这意味着它可以被任何兼容 ERC-721 或 ERC-1155 的代币使用。

动机

随着 NFT 成为以太坊生态系统中广泛使用的代币形式,并且被用于各种用例,现在是时候为它们标准化额外的实用程序了。 拥有在链上存储代币属性的能力,可以提高代币的效用,因为它促进了跨集合的交互性,并提供了属性的永久存储。

此 ERC 为以下领域的基于 ERC-721ERC-1155 的代币引入了新的实用程序:

跨集合交互性

以可预测的格式在链上存储属性允许跨集合的交互性。 这意味着代币的属性可以被任何外部智能合约使用,而无需代币知道外部智能合约。

例如,一个代币可以代表一个具有其属性集的游戏角色,并且可以在具有相同统计数据的无关游戏中使用,而无需从链下来源检索这些属性。 这确保了游戏正在使用的数据是合法的,并且没有被篡改以获得优势。

属性的永久存储

标准化的链上代币属性允许永久存储它们。

对于链下属性存储,只有在链下存储可用时,属性才可用。 如果存储被关闭,则属性将丢失。 通过链上属性存储,只要区块链可用,属性就可用。 这增加了代币的价值,因为它确保了只要代币存在,属性就可用。

代币演变

链上存储代币属性允许代币随着时间的推移而演变。 所有者的行为可能会影响代币的属性。 由于属性存储在链上,因此智能合约有能力在满足某些阈值时修改属性。 这使得代币更具互动性并反映所有者的奉献精神和努力。

动态状态跟踪

链上存储代币属性允许动态状态跟踪。 这些属性可用于跟踪代币及其所有者的状态。 这允许代币用于各种用例。 其中一个用例是供应链; 代币可以代表产品,其属性可用于跟踪产品从待定、已发货、已交付等状态的转换。

规范

接口

本文档中的关键词“必须”,“禁止”,“必需”,“应”,“不应”,“应该”,“不应该”,“推荐”,“可以”和“可选”应按照 RFC 2119 中的描述进行解释。

/// @title ERC-7508 公共链上 NFT 属性存储库
/// @dev See https://eips.ethereum.org/EIPS/eip-7508
/// @dev 注意:此接口的 ERC-165 标识符为 0x212206a8。

pragma solidity ^0.8.21;

interface IERC7508 is IERC165 {
    /**
     * @notice 支持的访问类型列表。
     * @return `Owner` 类型,只有所有者可以管理参数。
     * @return `Collaborator` 类型,只有合作者可以管理参数。
     * @return `OwnerOrCollaborator` 类型,只有所有者或合作者可以管理参数。
     * @return `TokenOwner` 类型,只有代币所有者可以管理其代币的参数。
     * @return `SpecificAddress` 类型,只有特定地址可以管理参数。
     */
    enum AccessType {
        Owner,
        Collaborator,
        OwnerOrCollaborator,
        TokenOwner,
        SpecificAddress
    }

    /**
     * @notice 用于表示地址属性的结构。
     * @return key 属性的键
     * @return value 属性的值
     */
    struct AddressAttribute {
        string key;
        address value;
    }

    /**
     * @notice 用于表示布尔属性的结构。
     * @return key 属性的键
     * @return value 属性的值
     */
    struct BoolAttribute {
        string key;
        bool value;
    }

    /**
     * @notice 用于表示字节属性的结构。
     * @return key 属性的键
     * @return value 属性的值
     */
    struct BytesAttribute {
        string key;
        bytes value;
    }

    /**
     * @notice 用于表示 int 属性的结构。
     * @return key 属性的键
     * @return value 属性的值
     */
    struct IntAttribute {
        string key;
        int256 value;
    }

    /**
     * @notice 用于表示字符串属性的结构。
     * @return key 属性的键
     * @return value 属性的值
     */
    struct StringAttribute {
        string key;
        string value;
    }

    /**
     * @notice 用于表示 uint 属性的结构。
     * @return key 属性的键
     * @return value 属性的值
     */
    struct UintAttribute {
        string key;
        uint256 value;
    }

    /**
     * @notice 用于通知侦听器已注册新集合以使用存储库。
     * @param collection 集合的地址
     * @param owner 集合所有者的地址; 有权管理访问控制的地址
     * @param registeringAddress 注册集合的地址
     * @param useOwnable 一个布尔值,指示集合是否使用 Ownable 扩展来验证
     *   所有者 (`true`) 或不 (`false`)
     */
    event AccessControlRegistration(
        address indexed collection,
        address indexed owner,
        address indexed registeringAddress,
        bool useOwnable
    );

    /**
     * @notice 用于通知侦听器已更新特定参数的访问控制设置。
     * @param collection 集合的地址
     * @param key 已更新访问控制设置的参数的名称
     * @param accessType 已更新访问控制设置的参数的 AccessType
     * @param specificAddress 已更新的特定地址
     */
    event AccessControlUpdate(
        address indexed collection,
        string key,
        AccessType accessType,
        address specificAddress
    );

    /**
     * @notice 用于通知侦听器已更新集合的元数据 URI。
     * @param collection 集合的地址
     * @param attributesMetadataURI 新的属性元数据 URI
     */
    event MetadataURIUpdated(
        address indexed collection,
        string attributesMetadataURI
    );

    /**
     * @notice 用于通知侦听器已添加或删除新的合作者。
     * @param collection 集合的地址
     * @param collaborator 合作者的地址
     * @param isCollaborator 一个布尔值,指示是否已添加合作者 (`true`) 或删除合作者
     *   (`false`)
     */
    event CollaboratorUpdate(
        address indexed collection,
        address indexed collaborator,
        bool isCollaborator
    );

    /**
     * @notice 用于通知侦听器已更新地址属性。
     * @param collection 集合地址
     * @param tokenId 代币 ID
     * @param key 属性的键
     * @param value 属性的新值
     */
    event AddressAttributeUpdated(
        address indexed collection,
        uint256 indexed tokenId,
        string key,
        address value
    );

    /**
     * @notice 用于通知侦听器已更新布尔属性。
     * @param collection 集合地址
     * @param tokenId 代币 ID
     * @param key 属性的键
     * @param value 属性的新值
     */
    event BoolAttributeUpdated(
        address indexed collection,
        uint256 indexed tokenId,
        string key,
        bool value
    );

    /**
     * @notice 用于通知侦听器已更新字节属性。
     * @param collection 集合地址
     * @param tokenId 代币 ID
     * @param key 属性的键
     * @param value 属性的新值
     */
    event BytesAttributeUpdated(
        address indexed collection,
        uint256 indexed tokenId,
        string key,
        bytes value
    );

    /**
     * @notice 用于通知侦听器已更新 int 属性。
     * @param collection 集合地址
     * @param tokenId 代币 ID
     * @param key 属性的键
     * @param value 属性的新值
     */
    event IntAttributeUpdated(
        address indexed collection,
        uint256 indexed tokenId,
        string key,
        int256 value
    );

    /**
     * @notice 用于通知侦听器已更新字符串属性。
     * @param collection 集合地址
     * @param tokenId 代币 ID
     * @param key 属性的键
     * @param value 属性的新值
     */
    event StringAttributeUpdated(
        address indexed collection,
        uint256 indexed tokenId,
        string key,
        string value
    );

    /**
     * @notice 用于通知侦听器已更新 uint 属性。
     * @param collection 集合地址
     * @param tokenId 代币 ID
     * @param key 属性的键
     * @param value 属性的新值
     */
    event UintAttributeUpdated(
        address indexed collection,
        uint256 indexed tokenId,
        string key,
        uint256 value
    );

    // ------------------- 访问控制 -------------------

    /**
     * @notice 用于检查指定的地址是否列为给定集合参数的合作者。
     * @param collaborator 要检查的地址。
     * @param collection 集合的地址。
     * @return isCollaborator_ 一个布尔值,指示该地址是否为给定集合的合作者 (`true`) 或不 (`false`)。
     */
    function isCollaborator(
        address collaborator,
        address collection
    ) external view returns (bool isCollaborator_);

    /**
     * @notice 用于检查指定的地址是否列为给定集合的特定地址
     *   参数。
     * @param specificAddress 要检查的地址。
     * @param collection 集合的地址。
     * @param key 属性的键
     * @return isSpecificAddress_ 一个布尔值,指示该地址是否为给定集合参数的特定地址
     *   (`true`) 或不 (`false`)。
     */
    function isSpecificAddress(
        address specificAddress,
        address collection,
        string memory key
    ) external view returns (bool isSpecificAddress_);

    /**
     * @notice 用于注册集合以使用 RMRK 代币属性存储库。
     * @dev  如果集合未实现 Ownable 接口,则 `useOwnable` 值必须设置为 `false`。
     * @dev 发出 {AccessControlRegistration} 事件。
     * @param collection 将使用 RMRK 代币属性存储库的集合的地址。
     * @param owner 集合所有者的地址。
     * @param useOwnable 一个布尔值,指示集合是否实现了 Ownable 接口以及是否
     *   应使用它来验证调用者是否为所有者 (`true`) 或使用手动设置的所有者地址
     *   (`false`)。
     */
    function registerAccessControl(
        address collection,
        address owner,
        bool useOwnable
    ) external;

    /**
     * @notice 用于管理特定参数的访问控制设置。
     * @dev 只有集合的 `owner` 才能调用此函数。
     * @dev 可能的 `accessType` 值为:
     *  [
     *      Owner,
     *      Collaborator,
     *      OwnerOrCollaborator,
     *      TokenOwner,
     *      SpecificAddress,
     *  ]
     * @dev 发出 {AccessControlUpdated} 事件。
     * @param collection 要管理的集合的地址。
     * @param key 属性的键
     * @param accessType 要应用于参数的访问控制类型。
     * @param specificAddress 要添加为允许管理给定地址的特定地址
     *   参数的地址。
     */
    function manageAccessControl(
        address collection,
        string memory key,
        AccessType accessType,
        address specificAddress
    ) external;

    /**
     * @notice 用于管理集合的合作者。
     * @dev `collaboratorAddresses` 和 `collaboratorAddressAccess` 数组的长度必须相同。
     * @dev 发出 {CollaboratorUpdate} 事件。
     * @param collection 集合的地址
     * @param collaboratorAddresses 要管理的合作者地址数组
     * @param collaboratorAddressAccess 一个布尔值数组,指示合作者地址是否应
     *   接收权限 (`true`) 或不 (`false`)。
     */
    function manageCollaborators(
        address collection,
        address[] memory collaboratorAddresses,
        bool[] memory collaboratorAddressAccess
    ) external;

    // ------------------- 元数据 URI -------------------

    /**
     * @notice 用于检索集合的属性元数据 URI,其中包含有关集合属性的所有信息。
     * @param collection 集合的地址
     * @return attributesMetadataURI 属性元数据的 URI
     */
    function getAttributesMetadataURIForCollection(
        address collection
    ) external view returns (string memory attributesMetadataURI);

    /**
     * @notice 用于设置集合的元数据 URI,其中包含有关集合属性的所有信息。
     * @dev 发出 {MetadataURIUpdated} 事件。
     * @param collection 集合的地址
     * @param attributesMetadataURI 属性元数据的 URI
     */
    function setAttributesMetadataURIForCollection(
        address collection,
        string memory attributesMetadataURI
    ) external;

    // ------------------- 获取器 -------------------

    /**
     * @notice 用于检索地址类型代币属性。
     * @param collection 集合地址
     * @param tokenId 代币 ID
     * @param key 属性的键
     * @return attribute 地址属性的值
     */
    function getAddressAttribute(
        address collection,
        uint256 tokenId,
        string memory key
    ) external view returns (address attribute);

    /**
     * @notice 用于检索布尔类型代币属性。
     * @param collection 集合地址
     * @param tokenId 代币 ID
     * @param key 属性的键
     * @return attribute 布尔属性的值
     */
    function getBoolAttribute(
        address collection,
        uint256 tokenId,
        string memory key
    ) external view returns (bool attribute);

    /**
     * @notice 用于检索字节类型代币属性。
     * @param collection 集合地址
     * @param tokenId 代币 ID
     * @param key 属性的键
     * @return attribute 字节属性的值
     */
    function getBytesAttribute(
        address collection,
        uint256 tokenId,
        string memory key
    ) external view returns (bytes memory attribute);

    /**
     * @notice 用于检索 uint 类型代币属性。
     * @param collection 集合地址
     * @param tokenId 代币 ID
     * @param key 属性的键
     * @return attribute uint 属性的值
     */
    function getUintAttribute(
        address collection,
        uint256 tokenId,
        string memory key
    ) external view returns (uint256 attribute);
    /**
     * @notice 用于检索字符串类型代币属性。
     * @param collection 集合地址
     * @param tokenId 代币 ID
     * @param key 属性的键
     * @return attribute 字符串属性的值
     */
    function getStringAttribute(
        address collection,
        uint256 tokenId,
        string memory key
    ) external view returns (string memory attribute);

    /**
     * @notice 用于检索 int 类型代币属性。
     * @param collection 集合地址
     * @param tokenId 代币 ID
     * @param key 属性的键
     * @return attribute uint 属性的值
     */
    function getIntAttribute(
        address collection,
        uint256 tokenId,
        string memory key
    ) external view returns (int256 attribute);

    // ------------------- 批量获取器 -------------------

    /**
     * @notice 用于获取代币的多个地址参数值。
     * @dev `AddressAttribute` 结构包含以下字段:
     *  [
     *     string key,
     *     address value
     *  ]
     * @param collections 集合的地址,与属性键的顺序相同。 如果所有代币都来自同一个集合,则该数组可以包含一个包含集合地址的元素。
     * @param tokenIds 代币的 ID,与属性键的顺序相同。 如果所有属性都属于同一个代币,则该数组可以包含一个包含代币 ID 的元素。
     * @param attributeKeys 要检索的地址键数组
     * @return attributes 地址数组,与属性键的顺序相同
     */
    function getAddressAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        string[] memory attributeKeys
    ) external view returns (address[] memory attributes);

    /**
     * @notice 用于获取代币的多个布尔参数值。
     * @dev `BoolAttribute` 结构包含以下字段:
     *  [
     *     string key,
     *     bool value
     *  ]
     * @param collections 集合的地址,与属性键的顺序相同。 如果所有代币都来自同一个集合,则该数组可以包含一个包含集合地址的元素。
     * @param tokenIds 代币的 ID,与属性键的顺序相同。 如果所有属性都属于同一个代币,则该数组可以包含一个包含代币 ID 的元素。
     * @param attributeKeys 要检索的布尔键数组
     * @return attributes 布尔数组,与属性键的顺序相同
     */
    function getBoolAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        string[] memory attributeKeys
    ) external view returns (bool[] memory attributes);

    /**
     * @notice 用于获取代币的多个字节参数值。
     * @dev `BytesAttribute` 结构包含以下字段:
     *  [
     *     string key,
     *     bytes value
     *  ]
     * @param collections 集合的地址,与属性键的顺序相同。 如果所有代币都来自同一个集合,则该数组可以包含一个包含集合地址的元素。
     * @param tokenIds 代币的 ID,与属性键的顺序相同。 如果所有属性都属于同一个代币,则该数组可以包含一个包含代币 ID 的元素。
     * @param attributeKeys 要检索的字节键数组
     * @return attributes 字节数组,与属性键的顺序相同
     */
    function getBytesAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        string[] memory attributeKeys
    ) external view returns (bytes[] memory attributes);

    /**
     * @notice 用于获取代币的多个 int 参数值。
     * @param collections 集合的地址,与属性键的顺序相同。 如果所有代币都来自同一个集合,则该数组可以包含一个包含集合地址的元素。
     * @param tokenIds 代币的 ID,与属性键的顺序相同。 如果所有属性都属于同一个代币,则该数组可以包含一个包含代币 ID 的元素。
     * @param attributeKeys 要检索的 int 键数组
     * @return attributes int 数组,与属性键的顺序相同
     */
    function getIntAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        string[] memory attributeKeys
    ) external view returns (int256[] memory attributes);

    /**
     * @notice 用于获取代币的多个字符串参数值。
     * @param collections 集合的地址,与属性键的顺序相同。 如果所有代币都来自同一个集合,则该数组可以包含一个包含集合地址的元素。
     * @param tokenIds 代币的 ID,与属性键的顺序相同。 如果所有属性都属于同一个代币,则该数组可以包含一个包含代币 ID 的元素。
     * @param attributeKeys 要检索的字符串键数组
     * @return attributes 字符串数组,与属性键的顺序相同
     */
    function getStringAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        string[] memory attributeKeys
    ) external view returns (string[] memory attributes);

    /**
     * @notice 用于获取代币的多个 uint 参数值。
     * @param collections 集合的地址,与属性键的顺序相同。 如果所有代币都来自同一个集合,则该数组可以包含一个包含集合地址的元素。
     * @param tokenIds 代币的 ID,与属性键的顺序相同。 如果所有属性都属于同一个代币,则该数组可以包含一个包含代币 ID 的元素。
     * @param attributeKeys 要检索的 uint 键数组
     * @return attributes uint 数组,与属性键的顺序相同
     */
    function getUintAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        string[] memory attributeKeys
    ) external view returns (uint256[] memory attributes);

    /**
     * @notice 用于一次检索任何类型的多个代币属性。
     * @dev `StringAttribute`、`UintAttribute`、`IntAttribute`、`BoolAttribute`、`AddressAttribute` 和 `BytesAttribute` 结构由
     *   以下字段(其中 `value` 具有适当的类型)组成:
     *  [
     *      key,
     *      value,
     *  ]
     * @param collection 集合地址
     * @param tokenId 代币 ID
     * @param addressKeys 要检索的地址类型属性键数组
     * @param boolKeys 要检索的布尔类型属性键数组
     * @param bytesKeys 要检索的字节类型属性键数组
     * @param intKeys 要检索的 int 类型属性键数组
     * @param stringKeys 要检索的字符串类型属性键数组
     * @param uintKeys 要检索的 uint 类型属性键数组
     * @return addressAttributes 地址数组,与 addressKeys 的顺序相同
     * @return boolAttributes 布尔数组,与 boolKeys 的顺序相同
     * @return bytesAttributes 字节数组,与 bytesKeys 的顺序相同
     * @return intAttributes int 数组,与 intKeys 的顺序相同
     * @return stringAttributes 字符串数组,与 stringKeys 的顺序相同
     * @return uintAttributes uint 数组,与 uintKeys 的顺序相同
     */
    function getAttributes(
        address collection,
        uint256 tokenId,
        string[] memory addressKeys,
        string[] memory boolKeys,
        string[] memory bytesKeys,
        string[] memory intKeys,
        string[] memory stringKeys,
        string[] memory uintKeys
    )
        external
        view
        returns (
            address[] memory addressAttributes,
            bool[] memory boolAttributes,
            bytes[] memory bytesAttributes,
            int256[] memory intAttributes,
            string[] memory stringAttributes,
            uint256[] memory uintAttributes
        );

    // ------------------- 准备预签名消息 -------------------

    /**
     * @notice 用于检索要签名的消息,以提交预签名的地址属性更改。
     * @param collection 接收属性的代币的集合智能合约的地址
     * @param tokenId 接收属性的代币的 ID
     * @param key 属性键
     * @param value 属性值
     * @param deadline 预签名交易的截止时间戳,超过此时间戳消息无效
     * @return message 要由授权帐户签名的原始消息
     */
    function prepareMessageToPresignAddressAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        address value,
        uint256 deadline
    ) external view returns (bytes32 message);

    /**
     * @notice 用于检索要签名的消息,以提交预签名的布尔属性更改。
     * @param collection 接收属性的代币的集合智能合约的地址
     * @param tokenId 接收属性的代币的 ID
     * @param key 属性键
     * @param value 属性值
     * @param deadline 预签名交易的截止时间戳,超过此时间戳消息无效
     * @return message 要由授权帐户签名的原始消息
     */
    function prepareMessageToPresignBoolAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        bool value,
        uint256 deadline
    ) external view returns (bytes32 message);

    /**
     * @notice 用于检索要签名的消息,以提交预签名的字节属性更改。
     * @param collection 接收属性的代币的集合智能合约的地址
     * @param tokenId 接收属性的代币的 ID
     * @param key 属性键
     * @param value 属性值
     * @param deadline 预签名交易的截止时间戳,超过此时间戳消息无效
     * @return message 要由授权帐户签名的原始消息
     */
    function prepareMessageToPresignBytesAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        bytes memory value,
        uint256 deadline
    ) external view returns (bytes32 message);

    /**
     * @notice 用于检索要签名的消息,以提交预签名的 int 属性更改。
     * @param collection 接收属性的代币的集合智能合约的地址
     * @param tokenId 接收属性的代币的 ID
     * @param key 属性键
     * @param value 属性值
     * @param deadline 预签名交易的截止时间戳,超过此时间戳消息无效
     * @return message 要由授权帐户签名的原始消息
     */
    function prepareMessageToPresignIntAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        int256 value,
        uint256 deadline
    ) external view returns (bytes32 message);

    /**
     * @notice 用于检索要签名的消息,以提交预签名的字符串属性更改。
     * @param collection 接收属性的代币的集合智能合约的地址
     * @param tokenId 接收属性的代币的 ID
     * @param key 属性键
     * @param value 属性值
     * @param deadline 预签名交易的截止时间戳,超过此时间戳消息无效
     * @return message 要由授权帐户签名的原始消息
     */
    function prepareMessageToPresignStringAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        string memory value,
        uint256 deadline
    ) external view returns (bytes32 message);

    /**
     * @notice 用于检索要签名的消息,以提交预签名的 uint 属性更改。
     * @param collection 接收属性的代币的集合智能合约的地址
     * @param tokenId 接收属性的代币的 ID
     * @param key 属性键
     * @param value 属性值
     * @param deadline 预签名交易的截止时间戳,超过此时间戳消息无效
     * @return message 要由授权帐户签名的原始消息
     */
    function prepareMessageToPresignUintAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        uint256 value,
        uint256 deadline
    ) external view returns (bytes32 message);

    // ------------------- 设置器 -------------------

    /**
     * @notice 用于设置地址属性。
     * @dev 发出 {AddressAttributeUpdated} 事件。
     * @param collection 接收属性的集合的地址
     * @param tokenId 代币 ID
     * @param key 属性键
     * @param value 属性值
     */
    function setAddressAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        address value
    ) external;

    /**
     * @notice 用于设置布尔属性。
     * @dev 发出 {BoolAttributeUpdated} 事件。
     * @param collection 接收属性的集合的地址
     * @param tokenId 代币 ID
     * @param key 属性键
     * @param value 属性值
     */
    function setBoolAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        bool value
    ) external;

    /**
     * @notice 用于设置字节属性。
     * @dev 发出 {BytesAttributeUpdated} 事件。
     * @param collection 接收属性的集合的地址
     * @param tokenId 代币 ID
     * @param key 属性键
     * @param value 属性值
     */
    function setBytesAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        bytes memory value
    ) external;

    /**
     * @notice 用于设置有符号数字属性。
     * @dev 发出 {IntAttributeUpdated} 事件。
     * @param collection 接收属性的集合的地址
     * @param tokenId 代币 ID
     * @param key 属性键
     * @param value 属性值
     */
    function setIntAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        int256 value
    ) external;

    /**
     * @notice 用于设置字符串属性。
     * @dev 发出 {StringAttributeUpdated} 事件。
     * @param collection 接收属性的集合的地址
     * @param tokenId 代币 ID
     * @param key 属性键
     * @param value 属性值
     */
    function setStringAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        string memory value
    ) external;

    /**
     * @notice 用于设置无符号数字属性。
     * @dev 发出 {UintAttributeUpdated} 事件。
     * @param collection 接收属性的集合的地址
     * @param tokenId 代币 ID
     * @param key 属性键
     * @param value 属性值
     */
    function setUintAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        uint256 value
    ) external;

    // ------------------- 批量设置器 -------------------

    /**
     * @notice 一次为代币设置多个地址属性。
     * @dev `AddressAttribute` 结构包含以下字段:
     *  [
     *      string key,
     *      address value
     *  ]
     * @param collections 集合的地址,与属性的顺序相同。 如果所有代币都来自同一个集合,则该数组可以包含一个包含集合地址的元素。
     * @param tokenIds 代币的 ID,与属性的顺序相同。 如果所有属性都属于同一个代币,则该数组可以包含一个包含代币 ID 的元素。
     * @param attributes 一个 `AddressAttribute` 结构数组,要分配给给定的代币
     */
    function setAddressAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        AddressAttribute[] memory attributes
    ) external;

    /**
     * @notice 一次为代币设置多个布尔属性。
     * @dev `BoolAttribute` 结构包含以下字段:
     *  [
     *      string key,
     *      bool value
     *  ]
     * @param collections 集合的地址,与属性的顺序相同。 如果所有代币都来自同一个集合,则该数组可以包含一个包含集合地址的元素。
     * @param tokenIds 代币的 ID,与属性的顺序相同。 如果所有属性都属于同一个代币,则该数组可以包含一个包含代币 ID 的元素。
     * @param attributes 一个 `BoolAttribute` 结构数组,要分配给给定的代币
     */
    function setBoolAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        BoolAttribute[] memory attributes
    ) external;

    /**
     * @notice 一次为代币设置多个字节属性。
     * @dev `BytesAttribute` 结构包含以下字段:
     *  [
     *      string key,
     *      bytes value
     *  ]
     * @param collections 集合的地址,与属性的顺序相同。 如果所有代币都来自同一个集合,则该数组可以包含一个包含集合地址的元素。
     * @param tokenIds 代币的 ID,与属性的顺序相同。 如果所有属性都属于同一个代币,则该数组可以包含一个包含代币 ID 的元素。
     * @param attributes 一个 `BytesAttribute` 结构数组,要分配给给定的代币
     */
    function setBytesAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        BytesAttribute[] memory attributes
    ) external;

    /**
     * @notice 一次为代币设置多个 int 属性。
     * @dev `UintAttribute` 结构包含以下字段:
     *  [
     *      string key,
     *      int value
     *  ]
     * @param collections 集合的地址,与属性的顺序相同。 如果所有代币都来自同一个集合,则该数组可以包含一个包含集合地址的元素。
     * @param tokenIds 代币的 ID,与属性的顺序相同。 如果所有属性都属于同一个代币,则该数组可以包含一个包含代币 ID 的元素。
     * @param attributes 一个 `IntAttribute` 结构数组,要分配给给定的代币
     */
    function setIntAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        IntAttribute[] memory attributes
    ) external;

    /**
     * @notice 一次为代币设置多个字符串属性。
     * @dev `StringAttribute` 结构包含以下字段:
     *  [
     *      string key,
     *      string value
     *  ]
     * @param collections 集合的地址,与属性的顺序相同。 如果所有代币都来自同一个集合,则该数组可以包含一个包含集合地址的元素。
     * @param tokenIds 代币的 ID,与属性的顺序相同。 如果所有属性都属于同一个代币,则```
/**
     * @notice 一次为 token 设置多个 uint 属性。
     * @dev `UintAttribute` 结构体包含以下字段:
     *  [
     *      string key,
     *      uint value
     *  ]
     * @param collections 集合的地址,与属性的顺序相同。如果所有 token 都来自同一个集合,则数组可以包含一个带有集合地址的元素。
     * @param tokenIds token 的 ID,与属性的顺序相同。如果所有属性都属于同一个 token,则数组可以包含一个带有 token ID 的元素。
     * @param attributes 要分配给给定 token 的 `UintAttribute` 结构体数组
     */
    function setUintAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        UintAttribute[] memory attributes
    ) external;

    /**
     * @notice 同时为 token 设置多个类型的多个属性。
     * @dev 为每个设置的属性发出单独的事件。
     * @dev `StringAttribute`、`UintAttribute`、`BoolAttribute`、`AddressAttribute` 和 `BytesAttribute` 结构体由以下字段组成(其中 `value` 具有适当的类型):
     *  [
     *      key,
     *      value,
     *  ]
     * @param collection 集合的地址
     * @param tokenId token ID
     * @param addressAttributes 包含要设置的地址属性的 `AddressAttribute` 结构体数组
     * @param boolAttributes 包含要设置的布尔属性的 `BoolAttribute` 结构体数组
     * @param bytesAttributes 包含要设置的字节属性的 `BytesAttribute` 结构体数组
     * @param intAttributes 包含要设置的整数属性的 `IntAttribute` 结构体数组
     * @param stringAttributes 包含要设置的字符串属性的 `StringAttribute` 结构体数组
     * @param uintAttributes 包含要设置的 uint 属性的 `UintAttribute` 结构体数组
     */
    function setAttributes(
        address collection,
        uint256 tokenId,
        AddressAttribute[] memory addressAttributes,
        BoolAttribute[] memory boolAttributes,
        BytesAttribute[] memory bytesAttributes,
        IntAttribute[] memory intAttributes,
        StringAttribute[] memory stringAttributes,
        UintAttribute[] memory uintAttributes
    ) external;

    // ------------------- 预签名设置器 -------------------

    /**
     * @notice 用于代表授权帐户设置地址属性。
     * @dev 发出 {AddressAttributeUpdated} 事件。
     * @param setter 预先签署属性更改的帐户地址
     * @param collection 接收属性的集合的地址
     * @param tokenId 接收属性的 token 的 ID
     * @param key 属性键
     * @param value 属性值
     * @param deadline 预签名交易的截止时间戳
     * @param v 预签名消息的 ECDSA 签名的 `v` 值
     * @param r 预签名消息的 ECDSA 签名的 `r` 值
     * @param s 预签名消息的 ECDSA 签名的 `s` 值
     */
    function presignedSetAddressAttribute(
        address setter,
        address collection,
        uint256 tokenId,
        string memory key,
        address value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @notice 用于代表授权帐户设置布尔属性。
     * @dev 发出 {BoolAttributeUpdated} 事件。
     * @param setter 预先签署属性更改的帐户地址
     * @param collection 接收属性的集合的地址
     * @param tokenId 接收属性的 token 的 ID
     * @param key 属性键
     * @param value 属性值
     * @param deadline 预签名交易的截止时间戳
     * @param v 预签名消息的 ECDSA 签名的 `v` 值
     * @param r 预签名消息的 ECDSA 签名的 `r` 值
     * @param s 预签名消息的 ECDSA 签名的 `s` 值
     */
    function presignedSetBoolAttribute(
        address setter,
        address collection,
        uint256 tokenId,
        string memory key,
        bool value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @notice 用于代表授权帐户设置字节属性。
     * @dev 发出 {BytesAttributeUpdated} 事件。
     * @param setter 预先签署属性更改的帐户地址
     * @param collection 接收属性的集合的地址
     * @param tokenId 接收属性的 token 的 ID
     * @param key 属性键
     * @param value 属性值
     * @param deadline 预签名交易的截止时间戳
     * @param v 预签名消息的 ECDSA 签名的 `v` 值
     * @param r 预签名消息的 ECDSA 签名的 `r` 值
     * @param s 预签名消息的 ECDSA 签名的 `s` 值
     */
    function presignedSetBytesAttribute(
        address setter,
        address collection,
        uint256 tokenId,
        string memory key,
        bytes memory value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @notice 用于代表授权帐户设置整数属性。
     * @dev 发出 {IntAttributeUpdated} 事件。
     * @param setter 预先签署属性更改的帐户地址
     * @param collection 接收属性的集合的地址
     * @param tokenId 接收属性的 token 的 ID
     * @param key 属性键
     * @param value 属性值
     * @param deadline 预签名交易的截止时间戳
     * @param v 预签名消息的 ECDSA 签名的 `v` 值
     * @param r 预签名消息的 ECDSA 签名的 `r` 值
     * @param s 预签名消息的 ECDSA 签名的 `s` 值
     */
    function presignedSetIntAttribute(
        address setter,
        address collection,
        uint256 tokenId,
        string memory key,
        int256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @notice 用于代表授权帐户设置字符串属性。
     * @dev 发出 {StringAttributeUpdated} 事件。
     * @param setter 预先签署属性更改的帐户地址
     * @param collection 接收属性的集合的地址
     * @param tokenId 接收属性的 token 的 ID
     * @param key 属性键
     * @param value 属性值
     * @param deadline 预签名交易的截止时间戳
     * @param v 预签名消息的 ECDSA 签名的 `v` 值
     * @param r 预签名消息的 ECDSA 签名的 `r` 值
     * @param s 预签名消息的 ECDSA 签名的 `s` 值
     */
    function presignedSetStringAttribute(
        address setter,
        address collection,
        uint256 tokenId,
        string memory key,
        string memory value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @notice 用于代表授权帐户设置 uint 属性。
     * @dev 发出 {UintAttributeUpdated} 事件。
     * @param setter 预先签署属性更改的帐户地址
     * @param collection 接收属性的集合的地址
     * @param tokenId 接收属性的 token 的 ID
     * @param key 属性键
     * @param value 属性值
     * @param deadline 预签名交易的截止时间戳
     * @param v 预签名消息的 ECDSA 签名的 `v` 值
     * @param r 预签名消息的 ECDSA 签名的 `r` 值
     * @param s 预签名消息的 ECDSA 签名的 `s` 值
     */
    function presignedSetUintAttribute(
        address setter,
        address collection,
        uint256 tokenId,
        string memory key,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;
}

Schema

除了接口之外,我们建议集合所有者应该能够为其集合的属性设置 schema。我们区分两种类型:token 属性和集合属性。后者是集合中所有 token 共享的属性,它们可以通过使用最大 uint256 值作为 tokenId 来检索和设置。

对于每个属性,我们指定以下字段:name(名称)、description(描述)、type(类型)、display_name(显示名称)、display_type(显示类型)、decimals(小数位数)、min_value(最小值)、max_value(最大值)、conditional_value(条件值)、modifiers(修饰符)和 multi_storage(多重存储)。只有名称和类型是必需的。有关这些字段的更多详细信息,请参阅可用的 [JSON schema]collection-metadata-schema.json,示例可在 collection-metadata-example.json 中找到。

conditional_value、modifiers 和 multi_storage 的理由将在理由部分讨论。

预签名属性的消息格式

为了让其他人提交属性设置,要由 setter 签名的消息格式如下:

keccak256(
        abi.encode(
            DOMAIN_SEPARATOR,
            METHOD_TYPEHASH,
            collection,
            tokenId,
            key,
            value,
            deadline
        )
    );

生成要签名的消息时传递的值为:

  • DOMAIN_SEPARATOR - 属性存储库智能合约的域分隔符
  • METHOD_TYPEHASH - 被调用方法的类型哈希。根据方法,支持的值为:
    • SET_UINT_ATTRIBUTE_TYPEHASH - 用于设置 uint 属性
    • SET_STRING_ATTRIBUTE_TYPEHASH - 用于设置字符串属性
    • SET_BOOL_ATTRIBUTE_TYPEHASH - 用于设置布尔属性
    • SET_BYTES_ATTRIBUTE_TYPEHASH - 用于设置字节属性
    • SET_ADDRESS_ATTRIBUTE_TYPEHASH - 用于设置地址属性
  • collection - 包含接收属性的 token 的集合的地址
  • tokenId - 接收属性的 token 的 ID
  • key - 属性键
  • value - 适当类型的属性值
  • deadline - 提交签名的截止时间的 UNIX 时间戳。在此截止时间之后提交的签名消息必须被拒绝

DOMAIN_SEPARATOR 的生成方式如下:

keccak256(
    abi.encode(
        "ERC-7508: Public Non-Fungible Token Attributes Repository",
        "1",
        block.chainid,
        address(this)
    )
);

SET_UINT_ATTRIBUTE_TYPEHASH 的生成方式如下:

keccak256(
    "setUintAttribute(address collection,uint256 tokenId,string memory key,uint256 value)"
);

SET_STRING_ATTRIBUTE_TYPEHASH 的生成方式如下:

keccak256(
    "setStringAttribute(address collection,uint256 tokenId,string memory key,string memory value)"
);

SET_BOOL_ATTRIBUTE_TYPEHASH 的生成方式如下:

keccak256(
    "setBoolAttribute(address collection,uint256 tokenId,string memory key,bool value)"
);

SET_BYTES_ATTRIBUTE_TYPEHASH 的生成方式如下:

keccak256(
    "setBytesAttribute(address collection,uint256 tokenId,string memory key,bytes memory value)"
);

SET_ADDRESS_ATTRIBUTE_TYPEHASH 的生成方式如下:

keccak256(
    "setAddressAttribute(address collection,uint256 tokenId,string memory key,address value)"
);

由于链 ID 不同,Attribute 存储库智能合约部署到的每个链都将具有不同的 DOMAIN_SEPARATOR 值。

Attributes 存储库的预定地址

Emotable 存储库智能合约的地址旨在类似于它所服务的功能。它以 0xA77B75 开头,这是 ATTBTS 的抽象表示。该地址待定。

理由

在设计提案时,我们考虑了以下问题:

  1. 我们是否应该将存储库存储的值称为 propertiers 还是 attributes?
    历史上,定义 token 特征的值被称为 properties,但已演变为称为 attributes。参考词典,property 被定义为某物具有的质量或特征,而 attribute 被定义为某人/某物的质量或特征。我们认为使用术语 attribute 更合适,因此决定使用它。
  2. 提案是否应指定访问控制?
    在设计提案时,我们有两种选择:要么在提案规范中包含访问控制,要么将访问控制留给希望使用属性存储库的实施者。在考虑这一点时,我们还必须考虑存储库的可用性和兼容性方面。
    一方面,包含访问控制会缩小实施的自由度,并要求实施者先对其进行配置才能使用存储库。另一方面,将访问控制留给实施者需要在其智能合约中专门设计属性访问控制,从而增加其大小、复杂性和部署成本。
    另一点需要注意的是,在提案中包含访问控制使其与在存储库部署之前存在的集合兼容,从而支持向后兼容性。
  3. 提案应该建立属性扩展还是公益存储库?
    最初,我们打算创建一个属性扩展以与任何兼容 ERC-721 的 token 一起使用。但是,我们意识到,如果提案是 token 属性的公益存储库,它将更有用。这样,可以利用它的 token 不仅是新的 token,而且是自提案发布之前就存在的老 token。
    这种修正的另一个好处是与 ERC-1155 token 的兼容性。
  4. 我们应该只包含单操作、只包含多操作,还是两者都包含?
    我们考虑过仅包含单操作,即用户只能为一个 token 分配单个属性,但我们决定同时包含单操作和多操作。这样,用户可以选择是将属性分配给单个 token 还是同时分配给多个 token。
    做出此决定是为了提案的长期可行性。根据网络的 gas 成本和集合中 token 的数量,用户可以选择最具成本效益的属性分配方式。
  5. 我们是否应该添加代表其他人分配属性的功能?
    虽然我们在起草提案时并没有打算将其作为提案的一部分添加,但我们意识到这将是一个有用的功能。这样,用户可以代表其他人分配属性,例如,如果他们自己无法执行此操作,或者属性是通过链下活动获得的。
  6. 我们如何确保代表其他人分配属性是合法的?
    我们可以向提案添加委托;当用户将其分配属性的权利委托给其他人时,但这样做会打开滥用和不正确设置属性的可能性。
    使用 ECDSA 签名,我们可以确保用户已同意代表他们分配属性。这样,用户可以使用属性的参数签名消息,并且签名可以由其他人提交。
  7. 在将属性分配给 token 时,我们是否应该添加链 ID 作为参数?
    我们决定不这样做,因为我们认为额外的参数很少使用,并且会增加属性分配交易的额外成本。如果集合智能合约想要利用链上 token 属性,则需要将反应记录在同一条链上。集成此提案的市场和钱包也将依赖于与属性位于同一链中,因为如果支持链 ID 参数,这将意味着他们需要在部署存储库的所有链上查询存储库智能合约,以获取给定 token 的属性。
    此外,如果集合创建者希望用户在不同的链上记录他们的反应,他们仍然可以指示用户这样做。存储库不验证被反应的 token 的存在(除非在属性可以由 token 的所有者修改的情况下),这在理论上意味着您可以将属性分配给不存在的 token 或尚未存在的 token。
  8. 我们应该如何降低存储库中字符串的使用成本? 我们在设计提案时处理的主要问题之一是字符串的使用成本。我们考虑使用字节而不是字符串,但由于这将要求用户自己编码和解码字符串,因此我们决定不这样做。
    降低成本的解决方案是使用字符串索引。这意味着设置新的字符串属性或键的成本将仅由第一个用户支付。随后的用户只需支付设置字符串属性或键的索引的成本。
    我们还将这种节省 gas 的方法扩展到适用于整个存储库。这意味着如果一个集合已经设置了字符串,则任何其他使用相同字符串的集合都不必再次支付设置字符串的成本。

向后兼容性

Attributes 存储库标准与 ERC-721ERC-1155 以及可用于 ERC-721 实施的强大工具以及现有的 ERC-721 基础设施完全兼容。

测试用例

测试包含在 attributesRepository.ts 中。

要在终端中运行它们,您可以使用以下命令:

cd ../assets/eip-7508
pnpm i
pnpm hardhat test

参考实现

请参阅 AttributesRepository.sol

安全注意事项

该提案不打算处理用户的任何形式的资产,因此与 Attributes 存储库交互时,资产不应处于风险之中。

使用 ECDSA 签名代表其他人设置属性的能力引入了重放攻击的风险,要签名的消息格式可以防止这种风险。消息中使用的 DOMAIN_SEPARATOR 对于部署到的链的存储库智能合约是唯一的。这意味着签名在任何其他链上都无效,并且部署在它们之上的属性存储库如果尝试重放攻击,应恢复该操作。

另一个需要考虑的是预签名消息重用的能力。由于消息包含签名有效性截止日期,因此可以在截止日期到达之前多次重用该消息。该提案仅允许为给定的键设置单个值,因此预签名消息不能被滥用以进一步修改属性值。但是,如果使用存储库的服务依赖于在某些操作之后恢复或修改属性的能力,则可以使用有效的预签名消息来重新分配 token 的属性。我们建议使用存储库结合预签名消息的服务使用在合理较短的时间后使预签名消息失效的截止日期。

在处理未经审计的合约时,建议谨慎行事。

版权

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

Citation

Please cite this document as:

Steven Pineda (@steven2308), Jan Turk (@ThunderDeliverer), "ERC-7508: 动态链上代币属性存储库 [DRAFT]," Ethereum Improvement Proposals, no. 7508, August 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7508.