Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor and adding interface #2

Merged
merged 1 commit into from
Dec 16, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 56 additions & 127 deletions EIPS/eip-5748.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ created: 2022-10-02
requires: 20
---


## Abstract

This standard adds an expiration time to the `_approve()` function of token contracts to automatically recall approvals within a certain time period. This functionality as a standard will prevent vulnerabilty exploits due to outstanding token approvals.
Expand All @@ -26,6 +25,60 @@ There have been many similar incidents to the Transit Swap attack. Security audi

## Specification

```solidity
pragma solidity ^0.8.0;

/// @title Approval Expiration for EIP-20 Tokens

interface IERC20ApprovalExpire {
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by a call to {approve} or {transferFrom}.
*/
event AllowanceExpirationUpdated(address indexed _owner, address indexed _spender, uint256 _value, uint256 _expireAt);


/**
* @dev returns the default expiration time for approval
*/
function defaultExpiration() external pure returns (uint256);

/**
* @dev Return the expire timestamp of the Approval which the address owner approve to the spender. _expiration[_owner][_spender]
*/
function allowanceExpiration(address _owner, address _spender) external view returns(uint256);

/**
* @dev
*
* Returns a boolean value indicating whether the operation succeeded.
*
* the origin ERC20 approve, increaseAllowance, decreaseAllowance functions need to use DEFAULT_EXPIRATION
* as the params ‘period’ when call this internal _approve function.
* In this function, set _expiration[owner][spender] = block.timestamp + period,
* and emit AllowanceExpirationUpdated event.
*
* Emits an {AllowanceExpirationUpdated} event.
* Emits an {Approval} event.
*
* In the success case
*/
function approve(address _spender, uint256 _value, uint256 _period) external returns (bool);

/**
* @dev Add the param 'period' for ERC20.increaseAllowance
* Default increaseAllowance is overridden if the period is specified
*/
function increaseAllowance(address _spender, uint256 _addedValue, uint256 _period) external returns (bool);

/**
* @dev Add the param 'period' for ERC20.decreaseAllowance
* Default decreaseAllowance is overridden if the period is specified
*/
function decreaseAllowance(address spender, uint256 subtractedValue, uint256 period) external returns (bool);
}

```

A time period `_expiration` is mapped on top of the original `allowance` to specify an approval time limit.

The `_approve()` method adds uint256 `period` to the entry, which is stored in `_expiration`.
Expand All @@ -38,134 +91,10 @@ In order not to interfere with the existing event statistics and to allow other

A separate approval method with the header `approve(address spender, uint256 amount, uint256 period)` modifies the `_expiration` value for existing approvals.

```solidity
contract ERC20 is Context, IERC20, IERC20Metadata {

mapping(address => uint256) private _balances;

mapping(address => mapping(address => uint256)) private _allowances;

uint256 private _totalSupply;

string private _name;
string private _symbol;

uint256 private constant DEFAULT_EXPIRATION = 86400 * 30;
mapping(address => mapping(address => uint256)) private _expiration;

event AllowanceExpireTimeUpdated(address indexed owner, address indexed spender, uint256 value, uint256 expiration);


function defaultExpiration() public pure returns (uint256) {
return DEFAULT_EXPIRATION;
}

function approve(address spender, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount, DEFAULT_EXPIRATION);
return true;
}


function approve(address spender, uint256 amount, uint256 period) public returns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount, period);
return true;
}


function _approve(
address owner,
address spender,
uint256 amount,
uint256 period
) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");

_allowances[owner][spender] = amount;
_expiration[owner][spender] = block.timestamp + period;
emit Approval(owner, spender, amount);
emit AllowanceExpireTimeUpdated(owner, spender, amount, _expiration[owner][spender]);
}
```

Transactions using the `transferFrom()` method will be checked against `_expiration` through `_spendAllowance()`. Transactions that take place after the alloted time period will fail.

```solidity
function transferFrom(
address from,
address to,
uint256 amount
) public virtual override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}


function _spendAllowance(
address owner,
address spender,
uint256 amount
) internal virtual {
uint256 currentAllowance = _allowances[owner][spender];
uint256 currentExpiration = _expiration[owner][spender];
require(block.timestamp <= currentExpiration, "Approval expired!");
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount, currentExpiration - block.timestamp);
}
}
}
```

The `allowance()`, `increaseAllowance()`, and `decreaseAllowance()` methods have been modified to accommodate the new features. `allowanceExpiration()` has been added to query the live period of the contract.

```solidity
function allowanceExpiration(address owner, address spender) public view returns (uint256) {
return _expiration[owner][spender];
}


function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue, DEFAULT_EXPIRATION);
return true;
}

function increaseAllowance(address spender, uint256 addedValue, uint256 period) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue, period);
return true;
}


function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue, DEFAULT_EXPIRATION);
}

return true;
}

function decreaseAllowance(address spender, uint256 subtractedValue, uint256 period) public virtual returns (bool) {
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue, period);
}

return true;
}
```

The above modifications adds a validity period to [EIP-20](./eip-20.md) token approvals. Users can freely update their allowance validity at any time by calling `approve(address spender, uint256 amount, uint256 period)`.

## Rationale
Expand All @@ -176,15 +105,15 @@ A separate approval method with the header `approve(address spender, uint256 amo

The internal method `_spendAllowance()` is introduced for code cleanliness with the function of checking the allowance amount and expiration.

The `allowance()` method has been modified to accommodate the functionality described in this standard. `allowanceExpiration()` has been added query the allowance expiration date.
The `allowance()` method has been modified to accommodate the functionality described in this standard. `allowanceExpiration()` has been added query the allowance expiration date.

## Backwards Compatibility

This standard is compatible with the [EIP-20](./eip-20.md), [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md) standards. Tokens issued in the form of proxy contracts can be updated to comply with this standard.

So that existing event statistics are not perturbed by the implementation of this standard, `_approve()` is triggered by the new event `AllowanceExpireTimeUpdated()`.

This standard is absolutely compatible with the traditional [EIP-20](./eip-20.md) standard. `DEFAULT_EXPIRATION` is added so that the original `approve(address spender, uint256 amount)` method header can remain for backwards compatibility and ease of programming. `DEFAULT_EXPIRATION` can be set to a constant or variable according to the developer's needs.
This standard is absolutely compatible with the traditional [EIP-20](./eip-20.md) standard. `DEFAULT_EXPIRATION` is added so that the original `approve(address spender, uint256 amount)` method header can remain for backwards compatibility and ease of programming. `DEFAULT_EXPIRATION` can be set to a constant or variable according to the developer's needs.

## Reference Implementation

Expand Down