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

Add EIP-5748: Approval Expiration for EIP-20 Tokens #5748

Closed
wants to merge 38 commits into from
Closed
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
7781c2c
commit draft erc20 approval exxpiration
Mr-Lucky Oct 4, 2022
83890f9
general edits
turanzv Oct 5, 2022
fd971da
replace EIP with standard
turanzv Oct 5, 2022
9127219
Merge branch 'ethereum:master' into master
Mr-Lucky Oct 5, 2022
3ef9e71
fix amount of lost currency
Mr-Lucky Oct 5, 2022
0556b62
Update EIPS/eip-draft_erc20_approval_expiration.md
turanzv Oct 7, 2022
6b9c8f5
adding eip number
turanzv Oct 7, 2022
314bccc
rename files to eip number
turanzv Oct 7, 2022
7b6cb76
Merge branch 'master' of github.com:GoPlusSecurity/EIPs
turanzv Oct 7, 2022
aed99f0
fix contact info
turanzv Oct 7, 2022
c5817f1
fix ERC references to comply w/ ERC-### format
turanzv Oct 7, 2022
51d77b1
fix ERC references to comply w/ ERC-### format
turanzv Oct 7, 2022
a7d068a
add discussions-to
turanzv Oct 7, 2022
f756e39
edit contact info
turanzv Oct 7, 2022
b74515a
updte discussion forum
turanzv Oct 7, 2022
506bdff
update discussion forum
turanzv Oct 7, 2022
93fa230
editing header
turanzv Oct 7, 2022
65d3750
Update EIPS/eip-5748.md
turanzv Oct 11, 2022
e6dd69c
add defaultExpiration method
Mr-Lucky Oct 11, 2022
b615e30
add getter and rename DEFAULT_EXPIRATION
turanzv Oct 11, 2022
470912b
Update EIPS/eip-5748.md
turanzv Oct 24, 2022
b0183f0
Update EIPS/eip-5748.md
turanzv Oct 24, 2022
d9ffc78
Update EIPS/eip-5748.md
turanzv Oct 24, 2022
96dc083
Update EIPS/eip-5748.md
turanzv Oct 24, 2022
8db8319
corrected discussions-to link
turanzv Oct 24, 2022
df609c6
Remove pre-abstract text
turanzv Oct 24, 2022
9a368a2
Correct tone in Specification section
turanzv Oct 24, 2022
77bc9b3
adding @xyfidea to authors
turanzv Oct 31, 2022
a11982f
moving snippet from rationale to Backwards Compatibility
turanzv Nov 3, 2022
86cc023
fixing ERC-20 references re @ballestar
turanzv Nov 3, 2022
836dc7e
fixing markdown linting
turanzv Nov 3, 2022
6fde6e8
adding links to ERCs in repo
turanzv Nov 3, 2022
1846db3
changing ERC references to ERC b/c 'EIP Walidator'
turanzv Nov 3, 2022
abfe631
refactor and adding interface
Ballestar Nov 12, 2022
84631f1
Merge pull request #2 from ballestar/master
turanzv Dec 16, 2022
5459e2f
adding @ballestar to contributors
turanzv Dec 16, 2022
d098211
fixing license on .sol asset
turanzv Dec 27, 2022
a1e8bb8
fixing @ballestar annotation
turanzv Dec 27, 2022
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
191 changes: 191 additions & 0 deletions EIPS/eip-5748.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
---
eip: 5748
title: Approval Expiration for EIP-20 Tokens
description: This standard introduces a standardized way for users to set expiration limits on ERC-20 token approvals.
turanzv marked this conversation as resolved.
Show resolved Hide resolved
author: Go+ Security (@GoPlusSecurity), Jeff Hall (@Mr-Lucky), Xavi (@XaaaaavitheFool), Turan Vural (@turanzv)
discussions-to: <https://ethereum-magicians.org/t/eip-5748-automatic-expiration-of-token-approvals/>
turanzv marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This link doesn't seem to exist.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀🐞🧑‍🔧✅

status: Draft
type: Standards Track
category: Interface
created: 2022-10-02) format
turanzv marked this conversation as resolved.
Show resolved Hide resolved
requires:
turanzv marked this conversation as resolved.
Show resolved Hide resolved
---

This standard introduces a standard way for users to set expiration limits on ERC-20 token approvals to prevent the loss of assets due to contractrual vulnerabilities or authorization attacks.
turanzv marked this conversation as resolved.
Show resolved Hide resolved

## 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This proposal doesn't add an expiration time, but rather a validity period after the transaction is included on-chain. I know it's a pedantic difference, but it's important from a security point of view.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this _approve coming from?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect _approve comes from a well known EIP-721 implementation? The standard itself doesn't define it.


## Motivation
As of the writing of this standard, the approval attack is the most common attack method in the industry with nearly all phishing attacks and asset theft related to it. Although good usage habits and basic knowledge of on-chain security can help users avoid a multitiude of exploits such as phishing, scams, DNS hijacking, JS injection, and other common approval attack methods, the spender's own security (or lack thereof) still needs to be fortified by strong code.

In the Transit Swap attack of October 1st, 2022, hackers used a vulnerability in the Transit Swap contract to steal $20 million in assets. Due in part to professional security audits and the strong brand recognition of Transit Swap, many users were given a false sense of security and trusted the platform's service. Transit Swaps's contract had been running smoothly for over a year and had accumulated a large amount of user allownces, making the contract a high-reward target for malicious actors.

There have been many similar incidents to the Transit Swap attack. Security audits cannot gurantee that a contract is 100% free from vulnerabilities. Some vulnerabilities are intentionally left by malicious developers themselves. In line with good trust practices and general security hygeine, unconditional trust is risky, and modifying approvals into limited trusts with automatic recovery will be a key tool to natively prevent attacks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This motivation is good background (though it might be polite to avoid mentioning a specific company/product), but it doesn't actually convince me that an expiring approval solves these problems. Maybe add a sentence along the lines of:

If these lingering approvals had instead expired, the number of users affected by this attack would have been much smaller.

## Specification

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does "mapped on top of" mean here?


The internal `_approve()` method adds a uint256 `period` to the entry and stored in `_expiration`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EIP-20 does not define an _approve() method. If you are referring to a particular implementation of EIP-20, don't. Instead, limit your specification to the visible interface of the contract and not any implementation details.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seen, edited

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


A `DEFAULT_EXPIRATION` constant is added for implementations that do not specify the `period` parameter. A getter method `defaultExpiration()` is included in the implementation for transparency to the user in the approval process.
turanzv marked this conversation as resolved.
Show resolved Hide resolved

In order not to interfere with the existing event statistics and to allow other developers to count the validity of allowance, we add a new event `AllowanceExpireTimeUpdated()`, which will be triggered in the `_approve()` method.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, the tone is off. You should state explicitly when compliant contracts emit the AllowanceExpireTimeUpdated event.


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

```solidity
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to Reference Implementation if you didn't intend to specify a specific behavior but only the interface.

contract ERC20 is Context, IERC20, IERC20Metadata {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using a contract, please only include an interface. EIPs should only specify the visible portion of a contract, and leave the implementation up to implementers.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


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 ERC-20 token approvals. Users can freely update their allowance validity at any time by calling `approve(address spender, uint256 amount, uint256 period)`.

## Rationale
`_expiration` is implemented as a mapping to improve the compatibility of the code.

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 ERC-20 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this paragraph to the Backwards Compatibility section.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

committed


A separate approval method with the header `approve(address spender, uint256 amount, uint256 period)` has been introduced to allow users to customize the `_expiration` value of the approval.

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.

## Backwards Compatibility
This standard is compatible with the ERC-20, ERC-721 and ERC-1155 standards. Tokens issued in the form of proxy contracts can be updated to comply with this standard.

## Reference Implementation
Implementation can be referenced in `../assets/eip-5748/`.

## Security Considerations
When upgrading a standard ERC-20-based proxy contract to this standard, attention should be paid to the location of assets.

## Copyright
Copyright and related rights waived via [CC0](../LICENSE.md).
Loading