Alert Source Discuss
Standards Track: ERC

ERC-5507: 可退款代币

为 ERC-20、ERC-721 和 ERC-1155 代币添加退款功能

Authors elie222 (@elie222), Gavin John (@Pandapip1)
Created 2022-08-19
Requires EIP-20, EIP-165, EIP-721, EIP-1155

摘要

本 ERC 为 ERC-20ERC-721ERC-1155 的初始代币发行添加了退款功能。资金被托管,直到预定的时间后才能被领取。在该预定时间过去之前,用户可以收到他们购买的代币的退款。

动机

NFT 和代币领域缺乏责任感。为了整个生态系统的健康,需要更好的机制来防止 rugpull 发生。提供退款为买家提供了更大的保护,并增加了创作者的合法性。

这种特定用例的标准接口具有以下优势:

  • 更符合欧盟的“远程销售法规”,该法规要求对在线购买的商品(如代币)提供 14 天的退款期
  • 与各种 NFT 相关的应用程序的互操作性,例如投资组合浏览器和市场
    • NFT 市场可以放置一个徽章,表明 NFT 仍然可以在列表上退款,并提供退款 NFT 而不是在市场上列出它们
    • 如果这样做可以产生更高的收益,DExes 可以提供退款代币
  • 更好的钱包确认对话框
    • 钱包可以更好地告知用户正在采取的行动(代币被退款),类似于转账通常有其自己独特的对话框
    • DAO 可以更好地显示包含退款代币的智能提案的功能

规范

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

所有实现必须使用并遵循 ERC-165 的指示。

ERC-20 退款扩展

// SPDX-License-Identifier: CC0-1.0

pragma solidity ^0.8.17;

import "ERC20.sol";
import "ERC165.sol";

/// @notice 可退款的 ERC-20 代币
/// @dev    该接口的 ERC-165 标识符为 `0xf0ca2917`
interface ERC20Refund is ERC20, ERC165 {
    /// @notice           当一个代币被退款时发出
    /// @dev              由 `refund` 发出
    /// @param  _from     资产被退款的帐户
    /// @param  _amount   被退款的代币数量(以不可分割的单位计算)
    event Refund(
        address indexed _from,
        uint256 indexed _amount
    );

    /// @notice           当一个代币被退款时发出
    /// @dev              由 `refundFrom` 发出
    /// @param  _sender   发送退款的帐户
    /// @param  _from     资产被退款的帐户
    /// @param  _amount   被退款的代币数量(以不可分割的单位计算)
    event RefundFrom(
        address indexed _sender,
        address indexed _from,
        uint256 indexed _amount
    );

    /// @notice         只要退款有效,就退款给用户
    /// @dev            确保检查用户是否拥有代币,并注意潜在的重入向量
    /// @param  amount  要退款的 `amount`
    function refund(uint256 amount) external;

    /// @notice         只要退款有效并且发送者有足够的批准,就退款代币并将以太币发送给发送者
    /// @dev            确保检查用户是否拥有代币,并注意潜在的重入向量
    ///                 以太币会发送给 msg.sender。
    /// @param  from    要从中退款资产的用户
    /// @param  amount  要退款的 `amount`
    function refundFrom(address from, uint256 amount) external;

    /// @notice         获取退款价格
    /// @return _wei    对于一个代币单位(10**decimals 不可分割的单位)将退还的以太币数量(以 wei 为单位)
    function refundOf() external view returns (uint256 _wei);
 
    /// @notice         获取退款无效的第一个区块
    /// @return block   代币无法退款的第一个区块
    function refundDeadlineOf() external view returns (uint256 block);
}

ERC-721 退款扩展

// SPDX-License-Identifier: CC0-1.0

pragma solidity ^0.8.17;

import "ERC721.sol";
import "ERC165.sol";

/// @notice 可退款的 ERC-721 代币
/// @dev    该接口的 ERC-165 标识符为 `0xe97f3c83`
interface ERC721Refund is ERC721 /* , ERC165 */ {
    /// @notice           当一个代币被退款时发出
    /// @dev              由 `refund` 发出
    /// @param  _from     资产被退款的帐户
    /// @param  _tokenId  被退款的 `tokenId`
    event Refund(
        address indexed _from,
        uint256 indexed _tokenId
    );

    /// @notice           当一个代币被退款时发出
    /// @dev              由 `refundFrom` 发出
    /// @param  _sender   发送退款的帐户
    /// @param  _from     资产被退款的帐户
    /// @param  _tokenId  被退款的 `tokenId`
    event RefundFrom(
        address indexed _sender,
        address indexed _from,
        uint256 indexed _tokenId
    );

    /// @notice         只要给定 `tokenId` 的退款有效,就退款给用户
    /// @dev            确保检查用户是否拥有代币,并注意潜在的重入向量
    /// @param  tokenId 要退款的 `tokenId`
    function refund(uint256 tokenId) external;

    /// @notice         只要退款有效并且发送者有足够的批准,就退款代币并将以太币发送给发送者
    /// @dev            确保检查用户是否拥有代币,并注意潜在的重入向量
    ///                 以太币会发送给 msg.sender。
    /// @param  from    要从中退款代币的用户
    /// @param  tokenId 要退款的 `tokenId`
    function refundFrom(address from, uint256 tokenId) external;

    /// @notice         获取特定 `tokenId` 的退款价格
    /// @param  tokenId 要查询的 `tokenId`
    /// @return _wei    将退还的以太币数量(以 wei 为单位)
    function refundOf(uint256 tokenId) external view returns (uint256 _wei);
 
    /// @notice         获取退款无效的给定 `tokenId` 的第一个区块
    /// @param  tokenId 要查询的 `tokenId`
    /// @return block   代币无法退款的第一个区块
    function refundDeadlineOf(uint256 tokenId) external view returns (uint256 block);
}

可选的 ERC-721 批量退款扩展

// SPDX-License-Identifier: CC0-1.0;

import "ERC721Refund.sol";

/// @notice 批量可退款的 ERC-721 代币
/// @dev    该接口的 ERC-165 标识符为 ``
contract ERC721BatchRefund is ERC721Refund {
    /// @notice           当一个或多个代币被批量退款时发出
    /// @dev              由 `refundBatch` 发出
    /// @param  _from     资产被退款的帐户
    /// @param  _tokenId  被退款的 `tokenIds`
    event RefundBatch(
        address indexed _from,
        uint256[] _tokenIds // 可能是索引的,也可能不是
    );

    /// @notice           当一个或多个代币被批量退款时发出
    /// @dev              由 `refundFromBatch` 发出
    /// @param  _sender   发送退款的帐户
    /// @param  _from     资产被退款的帐户
    /// @param  _tokenId  被退款的 `tokenId`
    event RefundFromBatch(
        address indexed _sender,
        address indexed _from,
        uint256 indexed _tokenId
    );
    
    /// @notice           只要给定 `tokenIds` 的退款有效,就退款给用户
    /// @dev              确保检查用户是否拥有代币,并注意潜在的重入向量
    ///                   这些必须一起成功或失败;没有部分退款。
    /// @param  tokenIds  要退款的 `tokenId`s
    function refundBatch(uint256[] tokenIds) external;

    /// @notice           只要给定 `tokenIds` 的退款有效并且发送者有足够的批准,就退款代币并将以太币发送给发送者
    /// @dev              确保检查用户是否拥有代币,并注意潜在的重入向量
    ///                   以太币会发送给 msg.sender。
    ///                   这些必须一起成功或失败;没有部分退款。
    /// @param  from      要从中退款代币的用户
    /// @param  tokenIds  要退款的 `tokenId`s
    function refundFromBatch(address from, uint256[] tokenIds) external;
}

ERC-1155 退款扩展

// SPDX-License-Identifier: CC0-1.0

pragma solidity ^0.8.17;

import "ERC1155.sol";
import "ERC165.sol";

/// @notice 可退款的 ERC-1155 代币
/// @dev    该接口的 ERC-165 标识符为 `0x94029f5c`
interface ERC1155Refund is ERC1155 /* , ERC165 */ {
    /// @notice           当一个代币被退款时发出
    /// @dev              由 `refund` 发出
    /// @param  _from     请求退款的帐户
    /// @param  _tokenId  被退款的 `tokenId`
    /// @param  _amount   被退款的 `tokenId` 的数量
    event Refund(
        address indexed _from,
        uint256 indexed _tokenId,
        uint256 _amount
    );

    /// @notice           当一个代币被退款时发出
    /// @dev              由 `refundFrom` 发出
    /// @param  _sender   发送退款的帐户
    /// @param  _from     资产被退款的帐户
    /// @param  _tokenId  被退款的 `tokenId`
    /// @param  _amount   被退款的 `tokenId` 的数量
    event RefundFrom(
        address indexed _sender,
        address indexed _from,
        uint256 indexed _tokenId
    );

    /// @notice         只要给定 `tokenId` 的退款有效,就退款给用户
    /// @dev            确保检查用户是否有足够的代币,并注意潜在的重入向量
    /// @param  tokenId 要退款的 `tokenId`
    /// @param  amount  要退款的 `tokenId` 的数量
    function refund(uint256 tokenId, uint256 amount) external;

    /// @notice         只要退款有效并且发送者有足够的批准,就退款代币并将以太币发送给发送者
    /// @dev            确保检查用户是否有足够的代币,并注意潜在的重入向量
    ///                 以太币会发送给 msg.sender。
    /// @param  from    要从中退款代币的用户
    /// @param  tokenId 要退款的 `tokenId`
    /// @param  amount  要退款的 `tokenId` 的数量
    function refundFrom(address from, uint256 tokenId, uint256 amount) external;

    /// @notice         获取特定 `tokenId` 的退款价格
    /// @param  tokenId 要查询的 `tokenId`
    /// @return _wei    对于单个代币将退还的以太币数量(以 wei 为单位)
    function refundOf(uint256 tokenId) external view returns (uint256 _wei);

    /// @notice         获取退款无效的给定 `tokenId` 的第一个区块
    /// @param  tokenId 要查询的 `tokenId`
    /// @return block   代币无法退款的第一个区块
    function refundDeadlineOf(uint256 tokenId) external view returns (uint256 block);
}

可选的 ERC-1155 批量退款扩展

// SPDX-License-Identifier: CC0-1.0;

import "ERC1155Refund.sol";

/// @notice 批量可退款的 ERC-1155 代币
/// @dev    该接口的 ERC-165 标识符为 ``
contract ERC1155BatchRefund is ERC1155Refund {
    /// @notice           当一个或多个代币被批量退款时发出
    /// @dev              由 `refundBatch` 发出
    /// @param  _from     请求退款的帐户
    /// @param  _tokenIds 被退款的 `tokenIds`
    /// @param  _amounts  每个被退款的 `tokenId` 的数量
    event RefundBatch(
        address indexed _from,
        uint256[] _tokenIds, // 可能是索引的,也可能不是
        uint256[] _amounts
    );

    /// @notice           当一个或多个代币被批量退款时发出
    /// @dev              由 `refundFromBatch` 发出
    /// @param  _sender   发送退款的帐户
    /// @param  _from     资产被退款的帐户
    /// @param  _tokenIds 被退款的 `tokenId`
    /// @param  _amounts  每个被退款的 `tokenId` 的数量
    event RefundFromBatch(
        address indexed _sender,
        address indexed _from,
        uint256[] _tokenId, // 可能是索引的,也可能不是
        uint256[] _amounts
    );
    
    /// @notice           只要给定 `tokenIds` 的退款有效,就退款给用户
    /// @dev              确保检查用户是否有足够的代币,并注意潜在的重入向量
    ///                   这些必须一起成功或失败;没有部分退款。
    /// @param  tokenIds  要退款的 `tokenId`s
    /// @param  amounts   要退款的每个 `tokenId` 的数量
    function refundBatch(uint256[] tokenIds, uint256[] amounts) external;

    /// @notice           只要给定 `tokenIds` 的退款有效并且发送者有足够的批准,就退款代币并将以太币发送给发送者
    /// @dev              确保检查用户是否拥有代币,并注意潜在的重入向量
    ///                   以太币会发送给 msg.sender。
    ///                   这些必须一起成功或失败;没有部分退款。
    /// @param  from      要从中退款代币的用户
    /// @param  tokenIds  要退款的 `tokenId`s
    /// @param  amounts   要退款的每个 `tokenId` 的数量
    function refundFromBatch(address from, uint256[] tokenIds, uint256[] amounts external;
}

理由

refundDeadlineOf 使用区块而不是时间戳,因为时间戳不如区块号可靠。

选择 refundrefundOfrefundDeadlineOf 的函数名称是为了适应 ERC-20、ERC-721 和 ERC-1155 的命名风格。

需要 ERC-165,因为如果不这样做,DApp 的自省会变得更加困难。

不支持自定义 ERC-20 代币,因为它不必要地增加了复杂性,并且 refundFrom 函数在与 DEx 结合使用时允许此功能。

批量退款是可选的,因为帐户抽象会使像这样的原子操作变得更加容易。但是,如果正确实施,它们仍然可以降低 gas 成本。

向后兼容性

未发现向后兼容性问题。

安全考虑

refund 函数存在潜在的重入风险。确保在销毁代币之后执行以太币转移(即遵守检查、效果、交互模式)。

版权

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

Citation

Please cite this document as:

elie222 (@elie222), Gavin John (@Pandapip1), "ERC-5507: 可退款代币," Ethereum Improvement Proposals, no. 5507, August 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5507.