forked from DecentralizedClimateFoundation/DCIPs
243 lines
10 KiB
Markdown
243 lines
10 KiB
Markdown
---
|
||
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 [ERC-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, ERC-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 ERC-20 tokens can delegate allowance enforcement to a proxy contract that implements this specification. An additional query function exists to get the underlying ERC-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 ERC-20 token contracts can delegate allowance enforcement to a proxy contract that implements this specification.
|
||
|
||
## Reference Implementation
|
||
|
||
An minimal implementation is included [here](../assets/eip-5827/ERC5827.sol)
|
||
|
||
An audited, open source implemention of this standard as a `IERC5827Proxy` can be found at `https://github.com/suberra/funnel-contracts`
|
||
|
||
## Security Considerations
|
||
|
||
This EIP introduces a stricter set of constraints compared to ERC-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 [ERC-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).
|