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

ERC: Token standard #20

Closed
frozeman opened this issue Nov 19, 2015 · 362 comments
Closed

ERC: Token standard #20

frozeman opened this issue Nov 19, 2015 · 362 comments

Comments

@frozeman
Copy link
Contributor

frozeman commented Nov 19, 2015

The final standard can be found here: https://eips.ethereum.org/EIPS/eip-20


ERC: 20
Title: Token standard
Status: Draft
Type: Informational
Created: 19-11.2015
Resolution: https://github.com/ethereum/wiki/wiki/Standardized_Contract_APIs

Abstract

The following describes standard functions a token contract can implement.

Motivation

Those will allow dapps and wallets to handle tokens across multiple interfaces/dapps.

The most important here are, transfer, balanceOf and the Transfer event.

Specification

Token

Methods

NOTE: An important point is that callers should handle false from returns (bool success). Callers should not assume that false is never returned!

totalSupply

function totalSupply() constant returns (uint256 totalSupply)

Get the total token supply

balanceOf

function balanceOf(address _owner) constant returns (uint256 balance)

Get the account balance of another account with address _owner

transfer

function transfer(address _to, uint256 _value) returns (bool success)

Send _value amount of tokens to address _to

transferFrom

function transferFrom(address _from, address _to, uint256 _value) returns (bool success)

Send _value amount of tokens from address _from to address _to

The transferFrom method is used for a withdraw workflow, allowing contracts to send tokens on your behalf, for example to "deposit" to a contract address and/or to charge fees in sub-currencies; the command should fail unless the _from account has deliberately authorized the sender of the message via some mechanism; we propose these standardized APIs for approval:

approve

function approve(address _spender, uint256 _value) returns (bool success)

Allow _spender to withdraw from your account, multiple times, up to the _value amount. If this function is called again it overwrites the current allowance with _value.

allowance

function allowance(address _owner, address _spender) constant returns (uint256 remaining)

Returns the amount which _spender is still allowed to withdraw from _owner

Events

Transfer

event Transfer(address indexed _from, address indexed _to, uint256 _value)

Triggered when tokens are transferred.

Approval

event Approval(address indexed _owner, address indexed _spender, uint256 _value)

Triggered whenever approve(address _spender, uint256 _value) is called.

History

Historical links related to this standard:

@frozeman
Copy link
Contributor Author

recent discussion from https://gist.github.com/frozeman/090ae32041bcfe120824

@vbuterin said:
Yeah, createCheque and cashCheque as above, plus transferCheque(address _from, address _to, uint256 _value) sounds good. In that case, we should probably remove the _to argument from cashCheque; generally, you can only cash cheques from your own bank account.

We probably also want getChequeValue(address _from, address _for). We then have a choice of whether we want to keep the value argument in cashCheque rather than simply only allowing cashing in 100% of whatever is in there. If we want to fully follow the cheque analogy, this triad seems most intuitive to me:

function createCheque(address _for, uint256 _maxValue)
function cashCheque(address _from)
function getChequeValue(address _from, address _for)

Question: does running createCheque twice add the value of the two cheques together? Are there legitimate use cases for creating a cheque multiple times and then cashing either once or multiple times?

@nmushegian said:
All the functions that return uint should return (uint, bool) instead. You can easily make up scenarios where a 0 return value is ambiguous and significant. Is there any other simpler pattern for handling this?

@niran said:
I think the value parameter is useful in cashCheque. It absolves callers from having to verify that the amount they needed was provided, and from having to refund amounts greater than what was needed. cashCheque would only succeed if the provided value was remaining in the cheque.
Also, I think using createCheque(2**100) for the approve use case is going to lead to less clear code. It gets better if you make the magic number a constant, like createCheque(UNLIMITED_CHEQUE_VALUE), but lots of people won't do that. I think it's worth having a createBlankCheque or something for the approve scenario. Most tokens will use the TokenLib to handle all of the cheque logic, so it doesn't really make things worse for token authors.

@caktux
I also think there is a problem with the terminology of cheques since they imply one-offs. Cheques are also unique, and here cheques wouldn't return unique IDs or anything; those are merely approval methods for transfers using internal bookkeeping. I think the current approve/transfer terminology is accurate and simple enough, instead of ending up with a mix of transfer and cashCheque. Would we change unapprove to tearCheque? There's also that ambiguity of cheques adding up, where approvals more clearly override a previous one.

In the use case described by Vitalik of contracts charging fees in subcurrencies, it could easily cost more to have to call approveOnce each time (if we replace the current approve method with it) than the actual fee in subcurrency. For that reason I think we should keep both approve and approveOnce, but we could add the _maxValue argument to the former, that way subscriptions or fees could be taken in multiple calls but only up to a certain amount. Another reason to keep the approval terminology, as I think it's much simpler to describe approve and approveOnce than some createMultiCheque and createCheque. Regarding isApprovedFor, it would have to return the approved amount if we do add _maxValue, just as isApprovedOnceFor does.

@ethers
Copy link
Member

ethers commented Nov 19, 2015

decimals() doesn't seem needed. The Token itself represents an indivisible unit. A higher-level, like SubCurrency, should use Token, SubCurrency is where decimals() or other things like symbol() could be implemented.

@ethers
Copy link
Member

ethers commented Nov 19, 2015

In all method descriptions, let's also remove "coin", eg "Get the total coin supply" -> "Get the total token supply"

@frozeman
Copy link
Contributor Author

I disagree, as the token is the currency or whatever and to represent it properly in any kind of dapp you need to know what is the proper way to represent that token. Ideally the user has to add only one contract address and the dapp can derive everything from there. Otherwise you make every dapp implement the low level token, plus some high level currency contract API. And not knowing the decimal points can be dangerous, otherwise one user sends 100.00 and another 100 (equals 1.00)

@simondlr
Copy link

I'm neither here not there about the terminology. I think "approve" OR "cheque" terminology is good enough.

At the end of the day, it seems we need both blanket approvals & once-offs. Or rather, it seems it would be useful to specify 3 things: 1) Total value that can be withdrawn, 2) How many times they can do that, & 3) How much at a time.

Spitballing another option:

Just one method, called approve (or setCustodian) that takes 2 parameters. How many times they are allowed to withdraw & how much each time?

approve(address _for, uint256 _withdraws, uint256 _max)

?

@frozeman Regarding names & other information. I agree with @ethers here. There will be tokens minted that don't have any requirement for names, symbols or decimals. Like prediction market outcomes or energy meter kwh tokens for example. This should not be at the token layer. All tokens are not automatically a sub-currency or coin (that uses additional information).

@ethers
Copy link
Member

ethers commented Nov 19, 2015

@frozeman
Copy link
Contributor Author

Created it here #22

@frozeman
Copy link
Contributor Author

@simondlr @ethers i think divisibility belongs to the token contract, as even non currency token can need that. Im fine with putting the name and symbol in the registry tho.

@ethers
Copy link
Member

ethers commented Nov 19, 2015

approve(address _address, uint256 _withdraws, uint256 _max) seems quite clean (suggested by @simondlr).
May tweak it as approve(address _address, uint256 _withdrawals, uint256 _maxValue)

(isApprovedFor then checks if there's at least 1 withdrawal approved for the amount)

EDIT: add address

@frozeman
Copy link
Contributor Author

You mean function approve(address _address, uint256 _withdraws, uint256 _max)

@simondlr
Copy link

@frozeman wrt decimals. Neither really here nor there about it. You can have multiple thresholds as is the case with Ether. The base token is still wei at the end of the day. You can't have decimal wei. You can write the wei in Ether, Finney, Shannon, etc. Each with their own decimal position. Should multiple thresholds be specified? Or only one? If so, is the token name specified to the base unit, or where the decimal point starts? ie Ether or Wei? It's a naming/usability convention, not a specification on how the token actually works.

Personally, for most new tokens that will be created, it doesn't really make sense to have them anymore. I do however see scenarios where it can be used. Thus, I'm neutral on this point. Don't mind. Perhaps it should be optional.

@frozeman
Copy link
Contributor Author

I should be optional sure, but for tokens which will be used in the wallet or user facing stuff, its quite necessary, except you don't want them to be divisible.

Concerning multiple steps, i think only one basic step is necessary from there on you can go in /100steps as usual, thats not hard to guess.

But it surely makes a difference if a user accidentally sends 10000 vs 100.00 token units ;)

@ethers
Copy link
Member

ethers commented Nov 19, 2015

No one wants many informational EIPs, but from a design perspective is it better to consider EIPs as composable and keep them modular and lean?

For example, an informational EIP for Wallet Tokens could be composed of EIP20, EIP22, EIPX?

Let's not forget https://gist.github.com/frozeman/090ae32041bcfe120824#gistcomment-1623513
As there's also been some discussion about the approve functionality, maybe approve APIs should also be its own EIP?

@frozeman
Copy link
Contributor Author

I think its best, when somebody makes a change proposal he list nicely formatted exactly what he would change. In the same as above in the first comment, then these changes are easier to understand, and later move into the actual proposal. Simply:

function myFunc(address _address, ...) returns (bool)

etc.

@simondlr
Copy link

Agreed @frozeman. wrt to this method.

function approve(address _address, uint256 _withdraws, uint256 _max) returns (bool success)

It's still slightly clunky as opposed to true or false (as you mentioned previously @niran). ie having to specify 2**100. We could add a helper function that replicates this internally? ie

function approveAll(address _address) returns (bool success)

it internally calls approve(_address, 2**100, 2**100)

Any other comments from others on this method?

@niran
Copy link

niran commented Nov 19, 2015

Is there a use case for specifying the number of withdrawals? If we're sticking with the approval language, I think the function signatures we started with were fine. You're always granting access to a certain amount of funds, and I don't see a case for caring about the increments those funds are withdrawn in.

Display information about tokens, like where to put the decimal point when displaying the native integer value, belongs in a registry. Changing a token contract is hard. Changing a registry entry is easy. decimals wouldn't change how the token contract works, nor how any calling contract would work. It's not necessary, and the UI problem will still be solved.

@alexvandesande
Copy link

@ethers decimals, name and symbol are important for displaying to the end user. If Ether was a token then the name would be "ether" with 8 decimal cases, and internally everything would be wei. Just like we do currently.

Regarding the approve/cheque discussion, I feel that we should always use focus on paving cow paths: implement what everyone is on absolute consensus as the basic "standard" and then allow real world usage dictate how to better define more advanced use cases.

@simondlr
Copy link

@niran true... If you have a subscription, then you someone can anyway withdraw all 12 months in 12 transactions (for example). A future layer could perhaps limit based on timestamps when new withdraws can be made. But let's leave that for later.

I agree with @alexvandesande. Let's keep it simple.

So we thus we only have 1 approve function?

function approve(address _for, uint256 _value) returns (bool success)

address _for can withdraw any amount up to _value. If approve is called again, it is not reset, it is simply added to that amount.

The transfer is difficult in this scenario, since it doesn't follow the cheque tango. Would it simple mean transferring ALL outstanding value to another custodian?

Perhaps we should leave that out for now & "pave the cowpaths" (love this saying). Find the "desire paths". :)


I understand why decimals are useful. It just won't matter in a substantial subset of tokens (you won't see them in wallet really). Thus it should be optional.

@aakilfernandes
Copy link

Just struck me, it would be great to have some kind of standardized system to pay miners with tokens.

You could imagine an user who holds a wallet of various coins, but doesn't know what Ether is. Rather than having to purchase Ether to make a transfer, there could be a _sendToMiner option in each function which pays the miner a fee in that token.

@nmushegian
Copy link

@simondlr I like this minimal viable approval function and also agree we should see what people build with it before making too broad a standard API

@simondlr
Copy link

I think tokens created from the wallets (like what @frozeman demo-ed at Devcon) can include decimals. Because it will be needed in that context (for example), which "paves the cowpaths". :)

@ethers
Copy link
Member

ethers commented Nov 19, 2015

decimals, name and symbol are important for displaying to the end user

Agree. Are we going to add them to EIP20, or leave them in EIP22?

@frozeman
Copy link
Contributor Author

I would add them to 20, but mark as optional

@frozeman
Copy link
Contributor Author

@simondlr i add your suggestion and also changed the order and names for isApprovedFor so it matches the function name:

function isApprovedFor(address _allowed, address _for) constant returns (bool success)

e.g. _allowed isApprovedFor _for

And this should probably return the still allowed value, e.g. if you already used up a part.
We should then probably rename this one too

@nmushegian
Copy link

^^ it's still ambiguous:

_allowed ( has allowed or is an approved holder for ) isApprovedFor _for

or

_allowed (is allowed or is an approved spender for ) isApprovedFor _for

Maybe holder and spender is more clear: function isApprovedFor(address _spender, address _holder) constant returns (bool _success)

@simondlr
Copy link

Yes, I would say isApprovedFor should return uint256 value, not bool anymore. I like @nmushegian's suggestion of holder & spender.

@Georgi87
Copy link

For Gnosis it would make sense to add an optional identifier, to handle multiple tokens within one contract. In our use case each outcome for each event represents one type of token. Without an identifier, one contract has to be created for each outcome. A lot of token contracts replicating the same functionality would be written on chain. We think there are many use cases for contracts controlling multiple tokens. E.g. also Weifund has to create a new contract for each campaign. Knowing that there are many use cases for both, we should define a standard for both. Since Solidity allows method overloading we can still use the same function names:

function transfer(address _to, uint256 _value) returns (bool _success)

function transfer(address _to, uint256 _value, bytes32 _identifier) returns (bool _success)

What do you think?

@termslang
Copy link

Proposed improvement to erc20 protocol:
Add optional (or maybe required?) getOwner() method.

The need for this emerges in our case of decentralised token exchange where only token owner is able to change the token's metadata like name of the company, web link etc (subject to decentralised token exchange implementation). There is no other way for such exchange to learn if the information provided is correct without sacrificing decentralisation.

@lichuan
Copy link

lichuan commented Jan 23, 2018

is this closed?

@frozeman
Copy link
Contributor Author

Yes: The final standard can be found here: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md

@Souptacular
Copy link
Contributor

Yes this is closed as @frozeman says.

@LefterisJP
Copy link
Contributor

@frozeman @Souptacular It could then be beneficial to lock this thread as no further discussion should happen here. HOWTO: https://help.github.com/articles/locking-conversations/

@3esmit
Copy link
Contributor

3esmit commented Jan 23, 2018

There is a lot of problems caused by ERC20, specifically the bad usage of transfer to contracts supporting usage of approval.
I think it would be interesting to block direct transfers to contracts, I mean, using the method transfer(address to, uint amount) to an address which codesizedata > 0. But this block would not exist in transferFrom(address from, address to, uint amount), or would be inverse (require codesizedata < 0) however not specifically important.
This restriction would prevent human errors from bad usage of ERC20 interface in wallets, where sending tokens directly to a contract.
However this changes could be also unnecessary if UI checked this stuff and would not enable send button, requiring a misleading action to be taken from terminal commands, this action should be coordinated between major wallets (and exchanges).

@Souptacular
Copy link
Contributor

Thanks @LefterisJP I'll lock it.

@ethereum ethereum locked as resolved and limited conversation to collaborators Jan 24, 2018
zhangxi2097 referenced this issue in zhangxi2097/ether Aug 11, 2018
Spiderworld referenced this issue in Spiderworld/gcg Apr 1, 2021
TajanB referenced this issue in TajanB/Tam Apr 28, 2021
tokenxszh referenced this issue in tokenxszh/xszh May 9, 2021
alom1000 referenced this issue in alom1000/Fotonion-USD-Token-FUSDT- Jun 24, 2021
Marsoin referenced this issue in Marsoin/MarsCoin Jul 13, 2021
ABITSHADOWDeFiTOKEN referenced this issue in ABITSHADOWDeFiTOKEN/abitshadow-A-BITCOIN-GOLD-COIN Aug 26, 2021
alexjr06 referenced this issue in bnb-chain/bsc-genesis-contract Dec 2, 2021
947061 referenced this issue in 947061/947061 Jul 27, 2022
advance987 referenced this issue in advance987/advance987 Aug 8, 2022
ScarpaCoin referenced this issue in ScarpaCoin/ScarpaCoin.sol Aug 22, 2022
mehedihasan12 referenced this issue in mehedihasan12/bep20token- Aug 22, 2022
Sonikanuganti referenced this issue in Sonikanuganti/airdrop-sample Feb 9, 2023
Gynchain referenced this issue in Gynchain/apeswap-token-lists May 12, 2023
HubSwirl referenced this issue in HubSwirl/Hub-Swirl May 16, 2023
Cyberscope-lab referenced this issue in smartcode-audits/GoatkingSmartAudits Jul 20, 2023
bitzaura2024 referenced this issue in BITZAURA-TECH/SmartContract May 31, 2024
Coreumv2 referenced this issue in Coreumv2/Coreumv2 Jul 2, 2024
Bearnais64 referenced this issue in Bearnais64/Challenge-scroll Oct 9, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests