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.