Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-5827: 自动续订额度扩展

允许自动续订额度批准的扩展

Authors zlace (@zlace0x), zhongfu (@zhongfu), edison0xyz (@edison0xyz)
Created 2022-10-22
Discussion Link https://ethereum-magicians.org/t/eip-5827-auto-renewable-allowance-extension/10392
Requires EIP-20, EIP-165

摘要

此扩展为 ERC-20 额度增加了一种可续订额度机制,其中 recoveryRate 定义了额度每秒恢复到初始最大批准额 amount 的 token 数量。

动机

目前,ERC-20 token 支持额度,token 所有者可以使用该额度允许消费方代表他们消费一定数量的 token 。然而,这在涉及定期付款的情况下(例如订阅、工资、定期直接成本平均购买)并不理想。

许多现有的 DApp 通过要求用户授予大量或无限额度来规避此限制。这存在安全风险,因为恶意 DApp 可能会耗尽用户账户中高达授权额度的资金,并且用户可能没有意识到授予额度的影响。

自动续订额度支持许多传统的金融概念,如信用额度和借记额度。账户所有者可以指定消费限额,并根据随时间恢复的额度来限制向账户收取的费用。

规范

本文档中的关键词“必须 (MUST)”,“不得 (MUST NOT)”,“需要 (REQUIRED)”,“应该 (SHALL)”,“不应该 (SHALL NOT)”,“应当 (SHOULD)”,“不应当 (SHOULD NOT)”,“推荐 (RECOMMENDED)”,“可以 (MAY)”和“可选 (OPTIONAL)”按照 RFC 2119 中的描述进行解释。

pragma solidity ^0.8.0;

interface IERC5827 /* is ERC20, ERC165 */ {
    /*
     * Note: the ERC-165 identifier for this interface is 0x93cd7af6.
     * 0x93cd7af6 ===
     *   bytes4(keccak256('approveRenewable(address,uint256,uint256)')) ^
     *   bytes4(keccak256('renewableAllowance(address,address)')) ^
     *   bytes4(keccak256('approve(address,uint256)') ^
     *   bytes4(keccak256('transferFrom(address,address,uint256)') ^
     *   bytes4(keccak256('allowance(address,address)') ^
     */

    /**
     * @notice  当可用额度小于转账金额时抛出。
     * @param   available       可用额度; 如果未设置则为 0
     */
    error InsufficientRenewableAllowance(uint256 available);

    /**
     * @notice  当设置任何额度时发出。
     * @dev     即使设置了不可续订的额度,也 MUST 发出;如果是这样,则
     * @dev     `_recoveryRate` 必须为 0。
     * @param   _owner          token 的所有者
     * @param   _spender        token 允许的消费方
     * @param   _value          授予消费方的初始和最大额度
     * @param   _recoveryRate   每秒恢复的金额
     */
    event RenewableApproval(
        address indexed _owner,
        address indexed _spender,
        uint256 _value,
        uint256 _recoveryRate
    );

    /**
     * @notice  最初授予 `_spender` `_value` 的额度,该额度随时间恢复
     * @notice  以 `_recoveryRate` 的速率恢复,最高可达 `_value` 的限制。
     * @dev     SHOULD 导致 `allowance(address _owner, address _spender)` 返回 `_value`,
     * @dev     当 `_recoveryRate` 大于 `_value` 时,SHOULD 抛出,并且必须发出
     * @dev     `RenewableApproval` 事件。
     * @param   _spender        token 允许的消费方
     * @param   _value          授予消费方的初始和最大额度
     * @param   _recoveryRate   每秒恢复的金额
     */
    function approveRenewable(
        address _spender,
        uint256 _value,
        uint256 _recoveryRate
    ) external returns (bool success);

    /**
     * @notice  返回授予 `_spender` 的额度批准的最大金额和恢复率
     * @notice  由 `_owner`。
     * @dev     当已授予不可续订的额度时,例如使用 `approve(address _spender, uint256 _value)`,
     * @dev     `amount` 也必须是初始批准金额。
     * @param    _owner         token 的所有者
     * @param   _spender        token 允许的消费方
     * @return  amount 授予消费方的初始和最大额度
     * @return  recoveryRate 每秒恢复的金额
     */
    function renewableAllowance(address _owner, address _spender)
        external
        view
        returns (uint256 amount, uint256 recoveryRate);

    /// Overridden ERC-20 functions

    /**
     * @notice  授予 `_spender` `_value` 的(非递增)额度,并清除任何现有的
     * @notice  可续订的额度。
     * @dev     如果
     * @dev     有任何可续订的额度,则必须清除集合 `_recoveryRate` 为 0。
     * @param   _spender        token 允许的消费方
     * @param   _value          授予消费方的额度
     */
    function approve(address _spender, uint256 _value)
        external
        returns (bool success);

    /**
    * @notice   使用调用者的额度将 `amount` 个 token 从 `from` 移动到 `to`。
    * @dev      当从调用者的额度中扣除 `amount` 时,使用的额度数量
    * @dev      SHOULD 包括自上次转账以来恢复的金额,但 MUST NOT 超过 `renewableAllowance(address _owner, address
    * @dev      _spender)` 返回的最大允许金额。
    * @dev      当额度不足时,还 SHOULD 抛出 `InsufficientRenewableAllowance`。
    * @param    from            token 所有者地址
    * @param    to              token 接收者
    * @param    amount          要转移的 token 数量
    */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);

    /**
     * @notice  返回 `_spender` 当前可消费的金额。
     * @dev     如果存在 `_owner` 和 `_spender` 的可续订额度,则返回的金额必须是 `block.timestamp` 的金额。
     * @param   _owner         token 的所有者
     * @param   _spender       token 允许的消费方
     * @return  remaining 当前时间点的剩余额度
     */
    function allowance(address _owner, address _spender)
        external
        view
        returns (uint256 remaining);
}

基本方法 approve(address _spender, uint256 _value) 必须将 recoveryRate 设置为 0。

必须更新 allowance()transferFrom() 以包含额度恢复逻辑。

approveRenewable(address _spender, uint256 _value, uint256 _recoveryRate) 必须将初始额度金额和最大额度限制(额度可以恢复到的金额)都设置为 _value

supportsInterface(0x93cd7af6) 必须返回 true

附加接口

Token 代理

现有的 ERC-20 token 可以将额度执行委托给实现此规范的代理合约。存在一个额外的查询函数来获取底层 ERC-20 token。

interface IERC5827Proxy /* is IERC5827 */ {

    /*
     * Note: the ERC-165 identifier for this interface is 0xc55dae63.
     * 0xc55dae63 ===
     *   bytes4(keccak256('baseToken()')
     */

    /**
     * @notice   获取正在代理的底层基本 token。
     * @return   baseToken 基本 token 的地址
     */
    function baseToken() external view returns (address);
}

代理上的 transfer() 函数不得发出 Transfer 事件(因为底层 token 已经发出了该事件)。

自动过期

interface IERC5827Expirable /* is IERC5827 */ {
    /*
     * Note: the ERC-165 identifier for this interface is 0x46c5b619.
     * 0x46c5b619 ===
     *   bytes4(keccak256('approveRenewable(address,uint256,uint256,uint64)')) ^
     *   bytes4(keccak256('renewableAllowance(address,address)')) ^
     */

    /**
     * @notice  最初授予 `_spender` `_value` 的额度,该额度随时间恢复
     * @notice  以 `_recoveryRate` 的速率恢复,最高可达 `_value` 的限制,并在
     * @notice  `_expiration` 到期。
     * @dev     当 `_recoveryRate` 大于 `_value` 时,SHOULD 抛出,并且必须发出
     * @dev     `RenewableApproval` 事件。
     * @param   _spender        token 允许的消费方
     * @param   _value          最初授予消费方的额度
     * @param   _recoveryRate   每秒恢复的金额
     * @param   _expiration     额度到期的 Unix 时间(以秒为单位)
     */
    function approveRenewable(
        address _spender,
        uint256 _value,
        uint256 _recoveryRate,
        uint64 _expiration
    ) external returns (bool success);

    /**
     * @notice  返回批准的最大金额、恢复率和到期时间戳。
     * @return  amount 授予消费方的初始和最大额度
     * @return  recoveryRate 每秒恢复的金额
     * @return  expiration 额度到期的 Unix 时间(以秒为单位)
     */
    function renewableAllowance(address _owner, address _spender)
        external
        view
        returns (uint256 amount, uint256 recoveryRate, uint64 expiration);
}

理由

可以使用每个时间周期的离散重置来实现可续订的额度。然而,连续的 recoveryRate 允许更灵活的用例,不受重置周期的限制,并且可以使用更简单的逻辑来实现。

向后兼容性

现有的 ERC-20 token合约可以将额度执行委托给实现此规范的代理合约。

参考实现

一个最小的实现包含在此处

可以在 https://github.com/suberra/funnel-contracts 找到此标准的经过审计的开源实现,作为 IERC5827Proxy

安全考虑

与具有无限额度的 ERC-20 相比,此 EIP 引入了一组更严格的约束。但是,当 _recoveryRate 设置为较大的值时,仍然可以通过多个交易转移大量资金。

不知道 ERC-5827 的应用程序可能会错误地推断出 allowance(address _owner, address _spender) 返回的值或包含在 Approval 事件中的值是 _spender 可以从 _owner 消费的最大 token 数量。情况可能并非如此,例如当 _owner 授予 _spender 可续订的额度时。

版权

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

Citation

Please cite this document as:

zlace (@zlace0x), zhongfu (@zhongfu), edison0xyz (@edison0xyz), "ERC-5827: 自动续订额度扩展 [DRAFT]," Ethereum Improvement Proposals, no. 5827, October 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5827.