-
Notifications
You must be signed in to change notification settings - Fork 5.2k
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-223: Token standard with event handling implementation #6485
Merged
Merged
Changes from all commits
Commits
Show all changes
61 commits
Select commit
Hold shift + click to select a range
b32a8a2
Add EIP 223: Token standard with event handling implementation
Dexaran 09f3879
Update eip-223.md
Dexaran 4c6e49d
Update eip-223.md
Dexaran 337c7a0
Update eip-223.md
Dexaran f3563ba
Update eip-223.md
Dexaran 74493a4
Update eip-223.md
Dexaran b305a79
Update eip-223.md
Dexaran 4523668
Update eip-223.md
Dexaran 537c9c6
Update eip-223.md
Dexaran 2f4595f
Update eip-223.md
Dexaran 0bad3d8
Update eip-223.md
Dexaran f13c699
Update eip-223.md
Dexaran a3d05a5
Update eip-223.md
Dexaran 0c8db1b
Update eip-223.md
Dexaran 4ef1631
Update eip-223.md
Dexaran b3f6e3c
Update eip-223.md
Dexaran f265fed
Update EIPS/eip-223.md
Dexaran 8e87c99
Update EIPS/eip-223.md
Dexaran 10502bc
Update EIPS/eip-223.md
Dexaran 489a021
Update EIPS/eip-223.md
Dexaran e691b31
Update EIPS/eip-223.md
Dexaran 8b8ebfb
Replaced `constant` function definitions with `view` to match modern …
Dexaran fda8c4c
Update EIPS/eip-223.md
Dexaran 5eb2b7c
Update EIPS/eip-223.md
Dexaran 8076fc2
Update EIPS/eip-223.md
Dexaran db8bdb4
Update EIPS/eip-223.md
Dexaran 797dfef
re-added email
Dexaran 7c3387a
Update EIPS/eip-223.md
Dexaran c888759
Manually committing formatting change
Pandapip1 2b387f7
Fix formatting stuff
Pandapip1 73bbb95
Fix author list
Pandapip1 8afcba5
Fix more formatting that Sam missed
Pandapip1 f51655b
Update EIPS/eip-223.md
Dexaran e61458a
Update EIPS/eip-223.md
Dexaran 1857d83
Update EIPS/eip-223.md
Dexaran c7a4cd9
Update eip-223.md
Dexaran 0628cef
Update eip-223.md
Dexaran 6f64550
Update eip-223.md
Dexaran 4024619
Update eip-223.md
Dexaran 88aff68
Update eip-223.md
Dexaran 0b54c09
Update eip-223.md
Dexaran 3090cc6
Update the code of Transfer(...) event
Dexaran 5f7b6ac
Fix function signature
Dexaran 7b67c2e
Moved the tokenReceived() paragraph directly below transfer() deifnition
Dexaran 6093c8f
Backwards compatibility considerations
Dexaran bf71b97
Update eip-223.md
Dexaran b8463c0
Update EIPS/eip-223.md
Dexaran 52da79f
Update EIPS/eip-223.md
Dexaran 71ef953
Update EIPS/eip-223.md
Dexaran 936eca6
Update EIPS/eip-223.md
Dexaran bbba42b
Update EIPS/eip-223.md
Dexaran bd965a3
Update EIPS/eip-223.md
Dexaran 5bf8c06
Update EIPS/eip-223.md
Dexaran 5735aaa
Update EIPS/eip-223.md
Dexaran ab5818b
Update EIPS/eip-223.md
Dexaran ba4b2d1
update GAS tested values
Dexaran 98e74ca
updated the value of transferFrom GAS cost
Dexaran 218324b
Apply suggestions from code review
Pandapip1 5b09164
Move receiver to bottom
Pandapip1 419550a
Remove old function
Pandapip1 1a74ba0
Lint
Pandapip1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,374 @@ | ||
--- | ||
eip: 223 | ||
title: 223 Token with communication model | ||
Pandapip1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
description: Token with event handling and communication model | ||
author: Dexaran (@Dexaran) | ||
discussions-to: https://ethereum-magicians.org/t/erc-223-token-standard/12894 | ||
status: Draft | ||
type: Standards Track | ||
category: ERC | ||
created: 2017-05-03 | ||
--- | ||
|
||
## Abstract | ||
|
||
The following describes an interface for fungible tokens that supports a `tokenReceived` callback to notify contract recipients when tokens are received. | ||
|
||
## Motivation | ||
|
||
This token introduces a communication model for contracts that can be utilized to straighten the behavior of contracts that interact with such tokens. Specifically, this proposal: | ||
|
||
1. Informs receiving contracts of tokens, as opposed to [ERC-20](./eip-20.md) where the recipient of a token transfer gets no notification. | ||
2. Is more gas-efficient when depositing tokens to contracts. | ||
3. Allows for `_data` recording for financial transfers. | ||
|
||
## Specification | ||
Pandapip1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Generally we recommend using the blurb from the EIP template referring to RFC 2119 to define "MUST", "SHOULD", etc. |
||
Contracts intending to receive these tokens MUST implement `tokenReceived`. | ||
|
||
Token transfers to contracts not implementing `tokenReceived` as described below MUST revert. | ||
|
||
Pandapip1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
### Token contract | ||
|
||
#### Token Methods | ||
|
||
##### `totalSupply` | ||
|
||
```solidity | ||
function totalSupply() view returns (uint256 totalSupply) | ||
``` | ||
|
||
Gets the total supply of the token. The functionality of this method is identical to that of ERC-20. | ||
|
||
##### `name` | ||
|
||
```solidity | ||
function name() view returns (string _name) | ||
Pandapip1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
``` | ||
|
||
Gets the name of the token. The functionality of this method is identical to that of ERC-20. | ||
|
||
OPTIONAL - This method can be used to improve usability, but interfaces and other contracts MUST NOT expect these values to be present. | ||
|
||
##### `symbol` | ||
|
||
```solidity | ||
function symbol() view returns (bytes32 _symbol) | ||
``` | ||
|
||
Gets the symbol of the token. The functionality of this method is identical to that of ERC-20. | ||
|
||
OPTIONAL - This method can be used to improve usability, but interfaces and other contracts MUST NOT expect these values to be present. | ||
|
||
##### `decimals` | ||
|
||
```solidity | ||
function decimals() view returns (uint8 _decimals) | ||
``` | ||
|
||
Gets the number of decimals of the token. The functionality of this method is identical to that of ERC-20. | ||
|
||
OPTIONAL - This method can be used to improve usability, but interfaces and other contracts MUST NOT expect these values to be present. | ||
|
||
##### `balanceOf` | ||
|
||
```solidity | ||
function balanceOf(address _owner) view returns (uint256 balance) | ||
``` | ||
|
||
Gets the account balance of another account with address `_owner`. The functionality of this method is identical to that of ERC-20. | ||
|
||
##### `transfer(address, uint)` | ||
|
||
```solidity | ||
function transfer(address _to, uint _value) returns (bool) | ||
``` | ||
|
||
This function must transfer tokens, and if `_to` is a contract, it must call the `tokenReceived(address, uint256, bytes calldata)` function of `_to`. If the `tokenReceived` function is not implemented in `_to` (recipient contract), then the transaction must fail and the transfer of tokens must be reverted. | ||
If `_to` is an externally owned address, then the transaction must be sent without executing `tokenReceived` in `_to`. | ||
`_data` can be attached to this token transaction, but it requires more gas. `_data` can be empty. | ||
|
||
##### `transfer(address, uint, bytes)` | ||
|
||
```solidity | ||
function transfer(address _to, uint _value, bytes calldata _data) returns (bool) | ||
``` | ||
|
||
This function must transfer tokens and invoke the function `tokenReceived (address, uint256, bytes)` in `_to`, if `_to` is a contract. If the `tokenReceived` function is not implemented in `_to` (recipient contract), then the transaction must fail and the transfer of tokens must not occur. | ||
If `_to` is an externally owned address (determined by the code size being zero), then the transaction must be sent without executing `tokenReceived` in `_to`. | ||
`_data` can be attached to this token transaction, but it requires more gas. `_data` can be empty. | ||
|
||
NOTE: A possible way to check whether the `_to` is a contract or an address is to assemble the code of `_to`. If there is no code in `_to`, then this is an externally owned address, otherwise it's a contract.116 | ||
|
||
#### Events | ||
|
||
##### `Transfer` | ||
|
||
```solidity | ||
event Transfer(address indexed _from, address indexed _to, uint256 _value) | ||
``` | ||
|
||
Triggered when tokens are transferred. Compatible with and similar to the ERC-20 `Transfer` event. | ||
|
||
### [ERC-223](./eip-223.md) Token Receiver | ||
|
||
#### Receiver Methods | ||
|
||
```solidity | ||
function tokenReceived(address _from, uint _value, bytes calldata _data) | ||
``` | ||
|
||
A function for handling token transfers, which is called from the token contract, when a token holder sends tokens. `_from` is the address of the sender of the token, `_value` is the amount of incoming tokens, and `_data` is attached data similar to `msg.data` of Ether transactions. It works by analogy with the fallback function of Ether transactions and returns nothing. | ||
|
||
NOTE: `msg.sender` will be a token-contract inside the `tokenReceived` function. It may be important to filter which tokens are sent (by token-contract address). The token sender (the person who initiated the token transaction) will be `_from` inside the `tokenReceived` function. | ||
|
||
IMPORTANT: This function must be named `tokenReceived` and take parameters `address`, `uint256`, `bytes` to match the function signature `0x8943ec02`. | ||
|
||
## Rationale | ||
|
||
This standard introduces a communication model by enforcing the `transfer` to execute a handler function in the destination address. This is an important security consideration as it is required that the receiver explicitly implements the token handling function. In cases where the receiver does not implements such function the transfer MUST be reverted. | ||
|
||
This standard sticks to the push transaction model where the transfer of assets is initiated on the senders side and handled on the receivers side. As the result, ERC-223 transfers are more gas-efficient while dealing with depositing to contracts as ERC-223 tokens can be deposited with just one transaction while ERC-20 tokens require at least two calls (one for `approve` and the second that will invoke `transferFrom`). | ||
|
||
- [ERC-20](./eip-20.md) deposit: `approve` ~46 gas, `transferFrom` ~75K gas | ||
|
||
- ERC-223 deposit: `transfer` and handling on the receivers side ~54K gas | ||
|
||
This standard introduces the ability to correct user errors by allowing to handle ANY transactions on the recipient side and reject incorrect or improper transactions. This tokens utilize ONE transferring method for both types of interactions with tokens and externally owned addresses which can simplify the user experience and allow to avoid possible user mistakes. | ||
|
||
One downside of the commonly used [ERC-20](./eip-20.md) standard that ERC-223 is intended to solve is that [ERC-20](./eip-20.md) implements two methods of token transferring: (1) `transfer` function and (2) `approve + transferFrom` pattern. Transfer function of [ERC-20](./eip-20.md) standard does not notify the receiver and therefore if any tokens are sent to a contract with the `transfer` function then the receiver will not recognize this transfer and the tokens can become stuck in the receivers address without any possibility of recovering them. | ||
|
||
ERC-223 is intended to simplify the interaction with contracts that are intended to work with tokens. ERC-223 utilizes a "deposit" pattern, similar to that of plain Ether. An ERC-223 deposit to a contract is a simple call of the `transfer` function. This is one transaction as opposed to two step process of `approve + transferFrom` depositing. | ||
|
||
This standard allows payloads to be attached to transactions using the `bytes calldata _data` parameter, which can encode a second function call in the destination address, similar to how `msg.data` does in an Ether transaction, or allow for public logging on chain should it be necessary for financial transactions. | ||
|
||
## Backwards Compatibility | ||
|
||
The interface of this token is similar to that of ERC-20 and most functions serve the same purpose as their analogues in ERC-20. | ||
`transfer(address, uint256, bytes calldata)` function is not backwards compatible with ERC-20 interface. | ||
|
||
ERC-20 tokens can be delivered to a non-contract address with `transfer` function. ERC-20 tokens can be deposited to a contract address with `approve` + `transferFrom` pattern. Depositing ERC-20 tokens to the contract address with `transfer` function will always result in token deposit not being recognized by the recipient contract. | ||
|
||
Here is an example of the contract code that handles ERC-20 token deposit. The following contract can accepts `tokenA` deposits. It is impossible to prevent deposits of tokenA or any other ERC-20 token to this contract that are made with `transfer` function. | ||
|
||
```solidity | ||
contract ERC20Receiver | ||
{ | ||
event Deposit(); | ||
address tokenA; | ||
function deposit(uint _value, address _token) public | ||
{ | ||
require(_token == tokenA); | ||
IERC20(_token).transferFrom(msg.sender, address(this), _value); | ||
emit Deposit(); | ||
} | ||
} | ||
``` | ||
|
||
ERC-223 tokens must be delivered to non-contract address or contract address in the same way with `transfer` function. | ||
|
||
Here is an example of the contract code that handles ERC-223 token deposit. The following contract can filter tokens and only accepts `tokenA`. Other ERC-223 tokens would be rejected. | ||
|
||
```solidity | ||
contract ERC223Receiver | ||
{ | ||
event Deposit(); | ||
address tokenA; | ||
function tokenReceived(address _from, uint _value, bytes memory _data) public | ||
{ | ||
require(msg.sender == tokenA); | ||
emit Deposit(); | ||
} | ||
} | ||
``` | ||
|
||
## Security Considerations | ||
|
||
This token utilizes the model similar to plain Ether behavior. Therefore replay issues must be taken into account. | ||
|
||
### Reference Implementation | ||
|
||
```solidity | ||
pragma solidity ^0.8.19; | ||
|
||
library Address { | ||
/** | ||
* @dev Returns true if `account` is a contract. | ||
* | ||
* This test is non-exhaustive, and there may be false-negatives: during the | ||
* execution of a contract's constructor, its address will be reported as | ||
* not containing a contract. | ||
* | ||
* > It is unsafe to assume that an address for which this function returns | ||
* false is an externally-owned account (EOA) and not a contract. | ||
*/ | ||
function isContract(address account) internal view returns (bool) { | ||
// This method relies in extcodesize, which returns 0 for contracts in | ||
// construction, since the code is only stored at the end of the | ||
// constructor execution. | ||
|
||
uint256 size; | ||
// solhint-disable-next-line no-inline-assembly | ||
assembly { size := extcodesize(account) } | ||
return size > 0; | ||
} | ||
|
||
/** | ||
* @dev Converts an `address` into `address payable`. Note that this is | ||
* simply a type cast: the actual underlying value is not changed. | ||
*/ | ||
function toPayable(address account) internal pure returns (address payable) { | ||
return payable(account); | ||
} | ||
} | ||
|
||
abstract contract IERC223Recipient { | ||
/** | ||
* @dev Standard ERC223 function that will handle incoming token transfers. | ||
* | ||
* @param _from Token sender address. | ||
* @param _value Amount of tokens. | ||
* @param _data Transaction metadata. | ||
*/ | ||
function tokenReceived(address _from, uint _value, bytes memory _data) public virtual; | ||
} | ||
|
||
/** | ||
* @title Reference implementation of the ERC223 standard token. | ||
*/ | ||
contract ERC223Token { | ||
|
||
/** | ||
* @dev Event that is fired on successful transfer. | ||
*/ | ||
event Transfer(address indexed from, address indexed to, uint value, bytes data); | ||
|
||
string private _name; | ||
string private _symbol; | ||
uint8 private _decimals; | ||
uint256 private _totalSupply; | ||
|
||
mapping(address => uint256) public balances; // List of user balances. | ||
|
||
/** | ||
* @dev Sets the values for {name} and {symbol}, initializes {decimals} with | ||
* a default value of 18. | ||
* | ||
* To select a different value for {decimals}, use {_setupDecimals}. | ||
* | ||
* All three of these values are immutable: they can only be set once during | ||
* construction. | ||
*/ | ||
|
||
constructor(string memory new_name, string memory new_symbol, uint8 new_decimals) | ||
{ | ||
_name = new_name; | ||
_symbol = new_symbol; | ||
_decimals = new_decimals; | ||
} | ||
|
||
/** | ||
* @dev Returns the name of the token. | ||
*/ | ||
function name() public view returns (string memory) | ||
{ | ||
return _name; | ||
} | ||
|
||
/** | ||
* @dev Returns the symbol of the token, usually a shorter version of the | ||
* name. | ||
*/ | ||
function symbol() public view returns (string memory) | ||
{ | ||
return _symbol; | ||
} | ||
|
||
/** | ||
* @dev Returns the number of decimals used to get its user representation. | ||
* For example, if `decimals` equals `2`, a balance of `505` tokens should | ||
* be displayed to a user as `5,05` (`505 / 10 ** 2`). | ||
* | ||
* Tokens usually opt for a value of 18, imitating the relationship between | ||
* Ether and Wei. This is the value {ERC223} uses, unless {_setupDecimals} is | ||
* called. | ||
* | ||
* NOTE: This information is only used for _display_ purposes: it in | ||
* no way affects any of the arithmetic of the contract, including | ||
* {IERC223-balanceOf} and {IERC223-transfer}. | ||
*/ | ||
function decimals() public view returns (uint8) | ||
{ | ||
return _decimals; | ||
} | ||
|
||
/** | ||
* @dev See {IERC223-totalSupply}. | ||
*/ | ||
function totalSupply() public view returns (uint256) | ||
{ | ||
return _totalSupply; | ||
} | ||
|
||
|
||
/** | ||
* @dev Returns balance of the `_owner`. | ||
* | ||
* @param _owner The address whose balance will be returned. | ||
* @return balance Balance of the `_owner`. | ||
*/ | ||
function balanceOf(address _owner) public view returns (uint256) | ||
{ | ||
return balances[_owner]; | ||
} | ||
|
||
/** | ||
* @dev Transfer the specified amount of tokens to the specified address. | ||
* Invokes the `tokenFallback` function if the recipient is a contract. | ||
* The token transfer fails if the recipient is a contract | ||
* but does not implement the `tokenFallback` function | ||
* or the fallback function to receive funds. | ||
* | ||
* @param _to Receiver address. | ||
* @param _value Amount of tokens that will be transferred. | ||
* @param _data Transaction metadata. | ||
*/ | ||
function transfer(address _to, uint _value, bytes calldata _data) public returns (bool success) | ||
{ | ||
// Standard function transfer similar to ERC20 transfer with no _data . | ||
// Added due to backwards compatibility reasons . | ||
balances[msg.sender] = balances[msg.sender] - _value; | ||
balances[_to] = balances[_to] + _value; | ||
if(Address.isContract(_to)) { | ||
IERC223Recipient(_to).tokenReceived(msg.sender, _value, _data); | ||
} | ||
emit Transfer(msg.sender, _to, _value, _data); | ||
return true; | ||
} | ||
|
||
/** | ||
* @dev Transfer the specified amount of tokens to the specified address. | ||
* This function works the same with the previous one | ||
* but doesn't contain `_data` param. | ||
* Added due to backwards compatibility reasons. | ||
* | ||
* @param _to Receiver address. | ||
* @param _value Amount of tokens that will be transferred. | ||
*/ | ||
function transfer(address _to, uint _value) public returns (bool success) | ||
{ | ||
bytes memory _empty = hex"00000000"; | ||
balances[msg.sender] = balances[msg.sender] - _value; | ||
balances[_to] = balances[_to] + _value; | ||
if(Address.isContract(_to)) { | ||
IERC223Recipient(_to).tokenReceived(msg.sender, _value, _empty); | ||
} | ||
emit Transfer(msg.sender, _to, _value, _empty); | ||
return true; | ||
} | ||
} | ||
``` | ||
|
||
## Copyright | ||
Dexaran marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Copyright and related rights waived via [CC0](../LICENSE.md). |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note to other editors: this eip is resurrecting #223