-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add EIP-5827: Auto-renewable allowance extension (#5827)
* feat: add EIP-5824 * chore: update title * chore: update links * fix: EIP number, relative links * fix: added renamed file * fix: add EIP-20 reference * Apply suggestions from code review Co-authored-by: Pandapip1 <[email protected]> * feat: keep interface optionally separated from EIP-20 * Apply suggestions from code review Co-authored-by: Neuti Yoo <[email protected]> * feat: add optional interfaces * refactor: renamed event and approve functions * refactor: added more functions to ERC165 hash * refactor: comment styles * fix: added expiration to query function * feat: change recoveryRate to uint256 * feat: added ERC165 hashes * fix: trailing space, added requirement * feat: added insufficient allowance error, clarified transfer event * fix: linter * fix: replace fancy double-quotes with u+22 * fix: prose style - avoid acronyms that may be unfamiliar to audience - reword sentences to flow better * fix: fix natspec docs in interfaces - make them actually work - split lines where required so max width is 100 chars - prose fixes - indentation * feat: add new security consideration about non-EIP-5827-aware apps e.g. token allowance revocation tools * fix: specify that the extensions should extend IERC5827 too * fix: clarify possibly awkward expression and fix typo in param name * fix: add Markdown link to first mention of EIP-5827 (kind of weird, though...) * fix: "MUST not" -> "MUST NOT" Co-authored-by: Pandapip1 <[email protected]> Co-authored-by: Neuti Yoo <[email protected]> Co-authored-by: zhongfu <[email protected]>
- Loading branch information
1 parent
aed2995
commit 3ef3772
Showing
1 changed file
with
236 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
--- | ||
eip: 5827 | ||
title: Auto-renewable allowance extension | ||
description: Extension to enable automatic renewals on allowance approvals | ||
author: zlace (@zlace0x), zhongfu (@zhongfu), edison0xyz (@edison0xyz) | ||
discussions-to: https://ethereum-magicians.org/t/eip-5827-auto-renewable-allowance-extension/10392 | ||
status: Draft | ||
type: Standards Track | ||
category: ERC | ||
created: 2022-10-22 | ||
requires: 20, 165 | ||
--- | ||
|
||
## Abstract | ||
|
||
This extension adds a renewable allowance mechanism to [EIP-20](./eip-20.md) allowances, in which a `recoveryRate` defines the amount of token per second that the allowance regains towards the initial maximum approval `amount`. | ||
|
||
## Motivation | ||
|
||
Currently, EIP-20 tokens support allowances, with which token owners can allow a spender to spend a certain amount of tokens on their behalf. However, this is not ideal in circumstances involving recurring payments (e.g. subscriptions, salaries, recurring direct-cost-averaging purchases). | ||
|
||
Many existing DApps circumvent this limitation by requesting that users grant a large or unlimited allowance. This presents a security risk as malicious DApps can drain users' accounts up to the allowance granted, and users may not be aware of the implications of granting allowances. | ||
|
||
An auto-renewable allowance enables many traditional financial concepts like credit and debit limits. An account owner can specify a spending limit, and limit the amount charged to the account based on an allowance that recovers over time. | ||
|
||
|
||
## Specification | ||
|
||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. | ||
|
||
```solidity | ||
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 Thrown when the available allowance is less than the transfer amount. | ||
* @param available allowance available; 0 if unset | ||
*/ | ||
error InsufficientRenewableAllowance(uint256 available); | ||
/** | ||
* @notice Emitted when any allowance is set. | ||
* @dev MUST be emitted even if a non-renewable allowance is set; if so, the | ||
* @dev `_recoveryRate` MUST be 0. | ||
* @param _owner owner of token | ||
* @param _spender allowed spender of token | ||
* @param _value initial and maximum allowance granted to spender | ||
* @param _recoveryRate recovery amount per second | ||
*/ | ||
event RenewableApproval( | ||
address indexed _owner, | ||
address indexed _spender, | ||
uint256 _value, | ||
uint256 _recoveryRate | ||
); | ||
/** | ||
* @notice Grants an allowance of `_value` to `_spender` initially, which recovers over time | ||
* @notice at a rate of `_recoveryRate` up to a limit of `_value`. | ||
* @dev SHOULD cause `allowance(address _owner, address _spender)` to return `_value`, | ||
* @dev SHOULD throw when `_recoveryRate` is larger than `_value`, and MUST emit a | ||
* @dev `RenewableApproval` event. | ||
* @param _spender allowed spender of token | ||
* @param _value initial and maximum allowance granted to spender | ||
* @param _recoveryRate recovery amount per second | ||
*/ | ||
function approveRenewable( | ||
address _spender, | ||
uint256 _value, | ||
uint256 _recoveryRate | ||
) external returns (bool success); | ||
/** | ||
* @notice Returns approved max amount and recovery rate of allowance granted to `_spender` | ||
* @notice by `_owner`. | ||
* @dev `amount` MUST also be the initial approval amount when a non-renewable allowance | ||
* @dev has been granted, e.g. with `approve(address _spender, uint256 _value)`. | ||
* @param _owner owner of token | ||
* @param _spender allowed spender of token | ||
* @return amount initial and maximum allowance granted to spender | ||
* @return recoveryRate recovery amount per second | ||
*/ | ||
function renewableAllowance(address _owner, address _spender) | ||
external | ||
view | ||
returns (uint256 amount, uint256 recoveryRate); | ||
/// Overridden ERC-20 functions | ||
/** | ||
* @notice Grants a (non-increasing) allowance of _value to _spender and clears any existing | ||
* @notice renewable allowance. | ||
* @dev MUST clear set `_recoveryRate` to 0 on the corresponding renewable allowance, if | ||
* @dev any. | ||
* @param _spender allowed spender of token | ||
* @param _value allowance granted to spender | ||
*/ | ||
function approve(address _spender, uint256 _value) | ||
external | ||
returns (bool success); | ||
/** | ||
* @notice Moves `amount` tokens from `from` to `to` using the caller's allowance. | ||
* @dev When deducting `amount` from the caller's allowance, the allowance amount used | ||
* @dev SHOULD include the amount recovered since the last transfer, but MUST NOT exceed | ||
* @dev the maximum allowed amount returned by `renewableAllowance(address _owner, address | ||
* @dev _spender)`. | ||
* @dev SHOULD also throw `InsufficientRenewableAllowance` when the allowance is | ||
* @dev insufficient. | ||
* @param from token owner address | ||
* @param to token recipient | ||
* @param amount amount of token to transfer | ||
*/ | ||
function transferFrom( | ||
address from, | ||
address to, | ||
uint256 amount | ||
) external returns (bool); | ||
/** | ||
* @notice Returns amount currently spendable by `_spender`. | ||
* @dev The amount returned MUST be as of `block.timestamp`, if a renewable allowance | ||
* @dev for the `_owner` and `_spender` is present. | ||
* @param _owner owner of token | ||
* @param _spender allowed spender of token | ||
* @return remaining allowance at the current point in time | ||
*/ | ||
function allowance(address _owner, address _spender) | ||
external | ||
view | ||
returns (uint256 remaining); | ||
} | ||
``` | ||
|
||
Base method `approve(address _spender, uint256 _value)` MUST set `recoveryRate` to 0. | ||
|
||
Both `allowance()` and `transferFrom()` MUST be updated to include allowance recovery logic. | ||
|
||
`approveRenewable(address _spender, uint256 _value, uint256 _recoveryRate)` MUST set both the initial allowance amount and the maximum allowance limit (to which the allowance can recover) to `_value`. | ||
|
||
`supportsInterface(0x93cd7af6)` MUST return `true`. | ||
|
||
### Additional interfaces | ||
|
||
**Token Proxy** | ||
|
||
Existing EIP-20 tokens can delegate allowance enforcement to a proxy contract that implements this specification. An additional query function exists to get the underlying EIP-20 token. | ||
|
||
```solidity | ||
interface IERC5827Proxy /* is IERC5827 */ { | ||
/* | ||
* Note: the ERC-165 identifier for this interface is 0xc55dae63. | ||
* 0xc55dae63 === | ||
* bytes4(keccak256('baseToken()') | ||
*/ | ||
/** | ||
* @notice Get the underlying base token being proxied. | ||
* @return baseToken address of the base token | ||
*/ | ||
function baseToken() external view returns (address); | ||
} | ||
``` | ||
|
||
The `transfer()` function on the proxy MUST NOT emit the `Transfer` event (as the underlying token already does so). | ||
|
||
**Automatic Expiration** | ||
|
||
```solidity | ||
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 Grants an allowance of `_value` to `_spender` initially, which recovers over time | ||
* @notice at a rate of `_recoveryRate` up to a limit of `_value` and expires at | ||
* @notice `_expiration`. | ||
* @dev SHOULD throw when `_recoveryRate` is larger than `_value`, and MUST emit | ||
* @dev `RenewableApproval` event. | ||
* @param _spender allowed spender of token | ||
* @param _value initial allowance granted to spender | ||
* @param _recoveryRate recovery amount per second | ||
* @param _expiration Unix time (in seconds) at which the allowance expires | ||
*/ | ||
function approveRenewable( | ||
address _spender, | ||
uint256 _value, | ||
uint256 _recoveryRate, | ||
uint64 _expiration | ||
) external returns (bool success); | ||
/** | ||
* @notice Returns approved max amount, recovery rate, and expiration timestamp. | ||
* @return amount initial and maximum allowance granted to spender | ||
* @return recoveryRate recovery amount per second | ||
* @return expiration Unix time (in seconds) at which the allowance expires | ||
*/ | ||
function renewableAllowance(address _owner, address _spender) | ||
external | ||
view | ||
returns (uint256 amount, uint256 recoveryRate, uint64 expiration); | ||
} | ||
``` | ||
|
||
## Rationale | ||
|
||
Renewable allowances can be implemented with discrete resets per time cycle. However, a continuous `recoveryRate` allows for more flexible use cases not bound by reset cycles and can be implemented with simpler logic. | ||
|
||
## Backwards Compatibility | ||
|
||
Existing EIP-20 token contracts can delegate allowance enforcement to a proxy contract that implements this specification. | ||
|
||
## Security Considerations | ||
|
||
This EIP introduces a stricter set of constraints compared to EIP-20 with unlimited allowances. However, when `_recoveryRate` is set to a large value, large amounts can still be transferred over multiple transactions. | ||
|
||
Applications that are not [EIP-5827](./eip-5827.md)-aware may erroneously infer that the value returned by `allowance(address _owner, address _spender)` or included in `Approval` events is the maximum amount of tokens that `_spender` can spend from `_owner`. This may not be the case, such as when a renewable allowance is granted to `_spender` by `_owner`. | ||
|
||
## Copyright | ||
|
||
Copyright and related rights waived via [CC0](../LICENSE.md). |