Alert Source Discuss
Standards Track: ERC

ERC-6381: 公共非同质化代币表情符号仓库

使用 Unicode 表情符号对任何非同质化代币做出反应。

Authors Bruno Škvorc (@Swader), Steven Pineda (@steven2308), Stevan Bogosavljevic (@stevyhacker), Jan Turk (@ThunderDeliverer)
Created 2023-01-22
Requires EIP-165

摘要

公共非同质化代币表情符号仓库标准通过允许对 NFT 进行表情反应,从而增强了 ERC-721ERC-1155 的交互实用性。

本提案引入了使用 Unicode 标准化表情符号对 NFT 做出反应的能力,该反应存储在一个公共非门控仓库智能合约中,该合约可在所有网络上的相同地址访问。

动机

随着 NFT 成为以太坊生态系统中一种广泛的代币形式,并被用于各种用例,现在是时候标准化它们的附加实用性了。 允许任何人与 NFT 交互,为拥有 NFT 引入了交互性,并解锁了基于反馈的 NFT 机制。

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

互动性

对 NFT 使用表情符号的能力为拥有 NFT 引入了互动性。 这可以反映表情发送者(对 NFT 使用表情的人)的钦佩,也可以是代币所有者执行的特定操作的结果。 在代币上积累表情可以增加其独特性和/或价值。

基于反馈的进化

对 NFT 的标准化链上反应允许基于反馈的进化。

当前的解决方案要么是专有的,要么是链下的,因此容易受到操纵和不信任。 能够跟踪链上的交互可以实现对给定代币的信任和客观评估。 将代币设计为在满足某些表情阈值时进化,可以激励与代币系列的互动。

估值

当前的 NFT 市场严重依赖于代币先前的销售价值、上市代币的最低价格以及市场提供的稀缺性数据。 没有实时表明对特定代币的钦佩或渴望。 允许用户对代币使用表情符号增加了潜在买家和卖家根据代币收集到的印象来评估代币价值的可能性。

规范

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

/// @title ERC-6381 非同质化代币的表情符号扩展
/// @dev 参见 https://eips.ethereum.org/EIPS/eip-6381
/// @dev 注意:此接口的 ERC-165 标识符为 0xd9fac55a。

pragma solidity ^0.8.16;

interface IERC6381 /*is IERC165*/ {
    /**
     * @notice 用于通知监听器具有指定 ID 的代币已被表情反应或反应已被撤销。
     * @dev 仅当表情符号的状态发生更改时,才必须发出该事件。
     * @param emoter 对代币做出表情反应或撤销反应的帐户地址
     * @param collection 包含被表情反应或撤销反应的代币的集合智能合约地址
     * @param tokenId 代币的 ID
     * @param emoji 表情符号的 Unicode 标识符
     * @param on 布尔值,表示是否对代币进行了表情反应 (`true`),或者反应是否已被撤销 (`false`)
     */
    event Emoted(
        address indexed emoter,
        address indexed collection,
        uint256 indexed tokenId,
        bytes4 emoji,
        bool on
    );

    /**
     * @notice 用于获取代币上特定表情符号的表情符号数量。
     * @param collection 包含正在检查表情符号数量的代币的集合地址
     * @param tokenId 要检查表情符号数量的代币的 ID
     * @param emoji 表情符号的 Unicode 标识符
     * @return 代币上带有表情符号的表情数量
     */
    function emoteCountOf(
        address collection,
        uint256 tokenId,
        bytes4 emoji
    ) external view returns (uint256);

    /**
     * @notice 用于获取一组代币上特定表情符号的表情符号数量。
     * @param collections 包含正在检查表情符号数量的代币的集合地址数组
     * @param tokenIds 要检查表情符号数量的代币的 ID 数组
     * @param emojis 表情符号的 Unicode 标识符数组
     * @return 代币上带有表情符号的表情数量数组
     */
    function bulkEmoteCountOf(
        address[] memory collections,
        uint256[] memory tokenIds,
        bytes4[] memory emojis
    ) external view returns (uint256[] memory);

    /**
     * @notice 用于获取有关指定地址是否在特定代币上使用了特定表情符号的信息。
     * @param emoter 我们要检查其对代币的反应的帐户地址
     * @param collection 包含要检查表情符号反应的代币的集合智能合约地址
     * @param tokenId 要检查表情符号反应的代币的 ID
     * @param emoji 要检查反应的 ASCII 表情符号代码
     * @return 一个布尔值,指示 `emoter` 是否在代币上使用了 `emoji` (`true`) 或未 (`false`)
     */
    function hasEmoterUsedEmote(
        address emoter,
        address collection,
        uint256 tokenId,
        bytes4 emoji
    ) external view returns (bool);

    /**
     * @notice 用于获取有关指定地址是否在特定代币上使用了特定表情符号的信息。
     * @param emoters 我们要检查其对代币的反应的帐户地址数组
     * @param collections 包含要检查表情符号反应的代币的集合智能合约地址数组
     * @param tokenIds 要检查表情符号反应的代币的 ID 数组
     * @param emojis 要检查反应的 ASCII 表情符号代码数组
     * @return 一个布尔值数组,指示 `emoter`s 是否在代币上使用了 `emoji`s (`true`) 或未 (`false`)
     */
    function haveEmotersUsedEmotes(
        address[] memory emoters,
        address[] memory collections,
        uint256[] memory tokenIds,
        bytes4[] memory emojis
    ) external view returns (bool[] memory);

    /**
     * @notice 用于获取要由 `emoter` 签名的消息,以便他人可以提交反应。
     * @param collection 包含要进行表情反应的代币的集合智能合约地址
     * @param tokenId 要进行表情反应的代币的 ID
     * @param emoji 表情符号的 Unicode 标识符
     * @param state 布尔值,表示是否进行表情 (`true`) 或撤消 (`false`) 表情
     * @param deadline 提交签名的截止时间的 UNIX 时间戳
     * @return 要由 `emoter` 签名的消息,以便他人可以提交反应
     */
    function prepareMessageToPresignEmote(
        address collection,
        uint256 tokenId,
        bytes4 emoji,
        bool state,
        uint256 deadline
    ) external view returns (bytes32);

    /**
     * @notice 用于获取多个要由 `emoter` 签名的消息,以便他人可以提交反应。
     * @param collections 包含要进行表情反应的代币的集合智能合约地址数组
     * @param tokenIds 要进行表情反应的代币的 ID 数组
     * @param emojis 表情符号的 Unicode 标识符数组
     * @param states 布尔值数组,表示是否进行表情 (`true`) 或撤消 (`false`) 表情
     * @param deadlines 提交签名的截止时间的 UNIX 时间戳数组
     * @return 要由 `emoter` 签名的消息数组,以便他人可以提交反应
     */
    function bulkPrepareMessagesToPresignEmote(
        address[] memory collections,
        uint256[] memory tokenIds,
        bytes4[] memory emojis,
        bool[] memory states,
        uint256[] memory deadlines
    ) external view returns (bytes32[] memory);

    /**
     * @notice 用于对代币进行表情反应或撤消表情反应。
     * @dev 如果尝试设置预先存在的状态,则不执行任何操作。
     * @dev 如果表情符号的状态发生更改,则必须发出 `Emoted` 事件。
     * @param collection 包含要进行表情反应的代币的集合地址
     * @param tokenId 要进行表情反应的代币的 ID
     * @param emoji 表情符号的 Unicode 标识符
     * @param state 布尔值,表示是否进行表情 (`true`) 或撤消 (`false`) 表情
     */
    function emote(
        address collection,
        uint256 tokenId,
        bytes4 emoji,
        bool state
    ) external;

    /**
     * @notice 用于对多个代币进行表情反应或撤消表情反应。
     * @dev 如果尝试设置预先存在的状态,则不执行任何操作。
     * @dev 如果表情符号的状态发生更改,则必须发出 `Emoted` 事件。
     * @dev 如果 `collections`、`tokenIds`、`emojis` 和 `states` 数组的长度不相等,则必须恢复。
     * @param collections 包含要进行表情反应的代币的集合地址数组
     * @param tokenIds 要进行表情反应的代币的 ID 数组
     * @param emojis 表情符号的 Unicode 标识符数组
     * @param states 布尔值数组,表示是否进行表情 (`true`) 或撤消 (`false`) 表情
     */
    function bulkEmote(
        address[] memory collections,
        uint256[] memory tokenIds,
        bytes4[] memory emojis,
        bool[] memory states
    ) external;

    /**
     * @notice 用于代表他人对代币进行表情反应或撤消表情反应。
     * @dev 如果尝试设置预先存在的状态,则不执行任何操作。
     * @dev 如果表情符号的状态发生更改,则必须发出 `Emoted` 事件。
     * @dev 如果 `collections`、`tokenIds`、`emojis` 和 `states` 数组的长度不相等,则必须恢复。
     * @dev 如果 `deadline` 已过,则必须恢复。
     * @dev 如果恢复的地址为零地址,则必须恢复。
     * @param emoter 预先签署表情符号的地址
     * @param collection 包含要进行表情反应的代币的集合智能合约地址
     * @param tokenId 要进行表情反应的代币的 ID
     * @param emoji 表情符号的 Unicode 标识符
     * @param state 布尔值,表示是否进行表情 (`true`) 或撤消 (`false`) 表情
     * @param deadline 提交签名的截止时间的 UNIX 时间戳
     * @param v 通过 `prepareMessageToPresignEmote` 获取的消息的 ECDSA 签名的 `v` 值
     * @param r 通过 `prepareMessageToPresignEmote` 获取的消息的 ECDSA 签名的 `r` 值
     * @param s 通过 `prepareMessageToPresignEmote` 获取的消息的 ECDSA 签名的 `s` 值
     */
    function presignedEmote(
        address emoter,
        address collection,
        uint256 tokenId,
        bytes4 emoji,
        bool state,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @notice 用于批量代表他人对代币进行表情反应或撤消表情反应。
     * @dev 如果尝试设置预先存在的状态,则不执行任何操作。
     * @dev 如果表情符号的状态发生更改,则必须发出 `Emoted` 事件。
     * @dev 如果 `collections`、`tokenIds`、`emojis` 和 `states` 数组的长度不相等,则必须恢复。
     * @dev 如果 `deadline` 已过,则必须恢复。
     * @dev 如果恢复的地址为零地址,则必须恢复。
     * @param emoters 预先签署表情符号的帐户地址数组
     * @param collections 包含要进行表情反应的代币的集合地址数组
     * @param tokenIds 要进行表情反应的代币的 ID 数组
     * @param emojis 表情符号的 Unicode 标识符数组
     * @param states 布尔值数组,表示是否进行表情 (`true`) 或撤消 (`false`) 表情
     * @param deadlines 提交签名的截止时间的 UNIX 时间戳
     * @param v 通过 `prepareMessageToPresignEmote` 获取的消息的 ECDSA 签名的 `v` 值数组
     * @param r 通过 `prepareMessageToPresignEmote` 获取的消息的 ECDSA 签名的 `r` 值数组
     * @param s 通过 `prepareMessageToPresignEmote` 获取的消息的 ECDSA 签名的 `s` 值数组
     */
    function bulkPresignedEmote(
        address[] memory emoters,
        address[] memory collections,
        uint256[] memory tokenIds,
        bytes4[] memory emojis,
        bool[] memory states,
        uint256[] memory deadlines,
        uint8[] memory v,
        bytes32[] memory r,
        bytes32[] memory s
    ) external;
}

用于预签名表情的消息格式

要由 emoter 签名的消息,以便他人可以提交反应,其格式如下:

keccak256(
        abi.encode(
            DOMAIN_SEPARATOR,
            collection,
            tokenId,
            emoji,
            state,
            deadline
        )
    );

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

  • DOMAIN_SEPARATOR - Emotable 仓库智能合约的域分隔符
  • collection - 包含要进行表情反应的代币的集合地址
  • tokenId - 要进行表情反应的代币的 ID
  • emoji - 表情符号的 Unicode 标识符
  • state - 布尔值,表示是否进行表情 (true) 或撤消 (false) 表情
  • deadline - 提交签名的截止时间的 UNIX 时间戳

DOMAIN_SEPARATOR 的生成方式如下:

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

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

Emotable 仓库的预定地址

Emotable 仓库智能合约的地址旨在类似于它所服务的功能。 它以 0x311073 开头,这是 EMOTE 的抽象表示。 地址是:

0x31107354b61A0412E722455A771bC462901668eA

理由

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

  1. 该提案是否支持自定义表情符号,还是仅支持 Unicode 指定的表情符号? 该提案仅接受 Unicode 标识符,这是一个 bytes4 值。 这意味着,虽然我们鼓励实施者使用标准化表情符号添加反应,但 Unicode 标准未涵盖的值可用于自定义表情符号。 唯一的缺点是显示反应的界面必须知道要渲染哪种图像,并且此类添加可能会限制在制作它们的界面或市场中。
  2. 该提案应该使用表情符号来传递 NFT 的印象还是其他方法? 印象可以使用用户提供的字符串或数值来完成,但我们决定使用表情符号,因为它们是一种公认的传递印象和情感的方式。
  3. 该提案应该建立可表情扩展还是公共物品仓库? 最初,我们着手创建一个可表情扩展,以便与任何符合 ERC-721 的代币一起使用。 但是,我们意识到,如果它是一个可表情代币的公共物品仓库,该提案将更有用。 这样,可以做出反应的代币不仅是新的代币,而且是自提案提出之前就已经存在的旧代币。 与此决定一致,我们决定计算仓库智能合约的确定性地址。 这样,任何 NFT 系列都可以使用该仓库,而无需搜索给定链上的地址。
  4. 我们应该只包括单操作、只包括多操作,还是两者都包括? 我们考虑过只包括单操作,即用户只能用单个表情符号对单个代币做出反应,但我们决定同时包括单操作和多操作。 这样,用户可以选择是对单个代币还是对多个代币同时进行表情或撤消表情。 做出此决定的目的是为了该提案的长期可行性。 根据网络的 Gas 成本和集合中代币的数量,用户可以选择最具成本效益的表情方式。
  5. 我们应该添加代表他人进行表情的功能吗? 虽然我们在起草时没有打算将其作为提案的一部分添加,但我们意识到这将是一个有用的功能。 这样,比如在某人无法自行进行表情或表情是通过链下活动获得的,用户可以代表他人进行表情。
  6. 我们如何确保代表他人进行表情的合法性? 我们可以向提案添加委托人; 当用户将其表情权委托给其他人时,委托人可以代表他们进行表情。 然而,这将给提案增加大量的复杂性和额外的逻辑。 使用 ECDSA 签名,我们可以确保用户已同意代表他们进行表情。 这样,用户可以使用表情参数签署消息,并且签名可以由其他人提交。
  7. 在对代币做出反应时,我们是否应该添加链 ID 作为参数? 在讨论提案的过程中,有人建议我们可以添加链 ID 作为对代币做出反应时的参数。 这将允许用户在一个链上对另一个链的代币做出表情反应。 我们反对这样做,因为我们认为额外的参数很少使用,并且会增加反应交易的额外成本。 如果集合智能合约想要利用链上表情来表达它们包含的代币,则它们需要将反应记录在同一链上。 集成此提案的市场和钱包也将依赖于反应与同一链上的反应,因为如果支持链 ID 参数,则意味着他们需要查询部署仓库智能合约的所有链,以便获得给定代币的反应。 此外,如果集合创建者希望用户在其他链上记录他们的反应,他们仍然可以指示用户这样做。 仓库不会验证正在反应的代币是否存在,这在理论上意味着您可以对不存在的代币或尚未存在的代币做出反应。 在另一个链上的相同地址上存在不同的集合的可能性非常低,因此用户可以使用另一个链上集合的地址进行反应,并且他们不太可能无意中对另一个集合的代币做出反应。

向后兼容性

表情符号仓库标准与 ERC-721 完全兼容,并且与可用于实施 ERC-721 的强大工具以及现有的 ERC-721 基础设施兼容。

测试用例

测试包含在 emotableRepository.ts 中。

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

cd ../assets/eip-6381
npm install
npx hardhat test

参考实现

请参见 EmotableRepository.sol

安全考虑

该提案没有设想处理用户的任何形式的资产,因此与表情符号仓库交互时,资产不应处于风险之中。

使用 ECDSA 签名代表他人进行表情的能力引入了重放攻击的风险,要签名的消息的格式可以防止这种攻击。 要签名的消息中使用的 DOMAIN_SEPARATOR 对于部署它的链的仓库智能合约是唯一的。 这意味着签名在任何其他链上都无效,如果尝试重放攻击,部署在这些链上的表情符号仓库应恢复该操作。

另一个需要考虑的事情是预签名消息重用的能力。 由于消息包含签名有效性截止日期,因此在截止日期到达之前,该消息可以被重用任意次数。 该提案仅允许对特定代币的给定表情符号进行单个反应,因此不能滥用预签名消息来增加代币上的反应计数。 但是,如果使用仓库的服务依赖于在某些操作后撤销反应的能力,则可以使用有效的预签名消息来重新反应代币。 我们建议结合使用预签名消息的仓库服务使用在合理较短时间后使预签名消息无效的截止日期。

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

版权

CC0 下放弃版权及相关权利。

Citation

Please cite this document as:

Bruno Škvorc (@Swader), Steven Pineda (@steven2308), Stevan Bogosavljevic (@stevyhacker), Jan Turk (@ThunderDeliverer), "ERC-6381: 公共非同质化代币表情符号仓库," Ethereum Improvement Proposals, no. 6381, January 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6381.