ERC: Token standard #20

Open
frozeman opened this Issue Nov 19, 2015 · 286 comments

Projects

None yet
@frozeman
Member
frozeman commented Nov 19, 2015 edited
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.

@frozeman
Member

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
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
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
Member

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

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).

@frozeman
Member

Created it here #22

@frozeman
Member

@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
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
Member

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

@simondlr

@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
Member

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
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
Member

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

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
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

@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

@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

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

@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

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
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
Member

I would add them to 20, but mark as optional

@frozeman
Member

@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

^^ 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

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

@Georgi87

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?

@frozeman
Member

@Georgi87 +1 we would need to do the same for balanceOf

@simondlr

You'll need to add identifiers for each function method. How are we going to work with optional, but useful standards? ie decimals, naming & identifiers? We should emphasise this as separate.

@frozeman
Member

Lets discuss this more.

I changed isApprovedFor to allowance:

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

// Returns true if `_spender ` is allowed to direct debit from `_address`

And renamed every occourance to _spender to make all variables consistent.

I also renamed the event, please @all take a look

@simondlr

@frozeman +1 for allowance. Much more clear. Although description should be:

"Returns the remaining allowance that _spender is allowed to withdraw from _address"?

@frozeman
Member

fixed, right?

@simondlr

fixed, right?

👍

@koeppelmann

I support the demand for an token identifier in the standard.
Lets think of tokens as tickets to a concert, m^3 freight in a ship, the right to consume an amount of energy at a specific place in a specific point of time.

In all these examples you have different tokens (different concert, different ships, different place) but other than that all others rules are the same and it makes a lot of sense to handle them in one contract. And very important - this is not only for gas saving reasons.

As a responsible DAPP creator that would allow other tokens to be used in the DAPP you want to check that the tokens do what they promise - it will not be enough that someone self registered the token at a registry. Therefore DAPPs would most likely have a whitelist of (token) contracts that have been reviewed manually.

Just for the pure fun I created a market on it: http://groupgnosis.com/dapp/#/markets/0x149b4a227ef92585d61211bd0a518b2e/0x1878ace41a67160c97a419b01f63b2b094d67ae54

If we had a good metric for the "success" of the token standard we could set of a futarchy. But I guess this is difficult to come up with - ether price is clearly not the right metric for such a minor decision.

@niran
niran commented Nov 20, 2015

We should avoid optional parts of EIPs as a design goal. They make it much harder to convey what an implementer has actually implemented. If we separate optional components into separate EIPs, some tokens will claim to support EIP N, while others will say they support EIP N and EIP N+1. Everything becomes much clearer.

I still don't understand the opposition to using registries for names and decimal places. Separating this cosmetic data from the token itself allows dapps to make their own decisions about what data about the token is required to display in their interface. It also lets dapps add new requirements we didn't think of in the future. A token can specify an admin who can set registry entries on its behalf, ideally by forwarding transactions through the token contract so registries can blindly allow entries to be set for msg.sender.

Consider the Bitcoin community's desire to switch to "bits" as the common denomination. With decimals baked into the token contract, this is impossible without just ignoring what the token contract says. With decimals in registries, the admin can update the name of the BitcoinToken unit to "bits" and the decimal from 8 to 2. If people don't like admin control of this, they will use registries with different access control policies.

We should let this problem solve itself by keeping it out of the token contracts and letting dapp authors do whatever they want, starting with an admin-controlled token registry. TokenContract.decimals will be ignored by clients eventually. It is inevitable. We shouldn't include it here.

Identifiers should make it in this EIP so contracts that consume dynamic tokens don't have to implement separate code paths to implement a separate identifier EIP. They should not be optional: either all calls should have identifiers or we should create an identifier EIP. But more importantly, we should wrap this thing up. I don't think it's the end of the world if identifiers don't make it in.

@nmushegian

I'm in favor of leaving multi-token controllers out of this proposal. I think registries and multi-asset controllers are fundamentally related and can be tackled simultaneously once we see some real use cases.

@koeppelmann

once we see some real use cases.

Gnosis is already real and Augur soon will be. Those tokens from this markets can be used for all kind of APPs since they are basically a payment that will be done if a specific event happens. In principal you could do a crowdfunding campaign with "Hillary Clinton Tokens" from a prediction market. This would mean: you contribute to this campaign but only under the condition that Hillary gets elected.

I am 100% sure there are more meaningful uses of prediction market tokens.

I don't think it's the end of the world if identifiers don't make it in.

Of course not. But why defining a standard that from the beginning excludes one of the very few already existing DAPPs.

@Georgi87

@koeppelmann +1
If we know a use case already we should try to find a standard for it. Not every app has to implement the full standard anyways. Apps implementing the identifier can still work with tokens without the identifier. Our crowdfunding contract allwos to use tokens with identifier but still works with tokens without identifier without additional code, because Solidity ignores additional function arguments.

@ethers
Member
ethers commented Nov 21, 2015

I agree with the points @niran articulated:
#20 (comment)

Since there are several things being discussed (eg what should be optional, should there be separate EIPs, adding identifiers), does this type of "poll" help to clarify where "consensus" might be?
https://github.com/ethereum/wiki/wiki/Poll-for-token-proposal-EIP-20

@nmushegian

Our crowdfunding contract allwos to use tokens with identifier but still works with tokens without identifier without additional code, because Solidity ignores additional function arguments.

The function signatures would be different so it wouldn't work, you need to implement the functions directly

@Georgi87

The function signatures would be different so it wouldn't work, you need to implement the functions directly

If you have a token contract implementing the token interface without identifier and another contract is using this token contract with identifier, the identifier will be ignored by the token contract.

@nmushegian

@Georgi87 No, it will route to a different function signature:

import 'dapple/test.sol';

contract WithIdentifier {
    bool public transferred;
    bool public missed;
    function transfer( address to, uint amount, bytes32 id ) returns (bool ok) {
        transferred = true;
        return true;
    }
    function() {
        missed = true;
    }
}

contract NoIdentifier {
    function transfer( address to, uint amount ) returns (bool ok);
}


contract SignatureBehaviorTest is Test {
    WithIdentifier wid;
    function setUp() {
        wid = new WithIdentifier();
    }
    function testHypothesis() {
        NoIdentifier(wid).transfer( address(this), 100 );
        assertTrue( wid.transferred() );
    }
    function testReality() {
        NoIdentifier(wid).transfer(address(this), 100);
        assertTrue( wid.missed() );
        assertFalse( wid.transferred() );
        wid.transfer( address(this), 100, "identifier" );
        assertTrue( wid.transferred() );
    }
}
Testing SignatureBehaviorTest
  testHypothesis
    LOG: assertTrue was false
    Fail!
  testReality
@nmushegian nmushegian pushed a commit to nexusdev/dappsys that referenced this issue Nov 21, 2015
nikolai token interface ethereum/EIPs#20 cf3d630
@Georgi87

Thank you for posting this test @nmushegian. There was an error in my test case.

@nmushegian

@Georgi87 I submitted the behavior you were expecting as a feature request: ethereum/solidity#240

@frozeman frozeman added the ERC label Nov 23, 2015
@joeykrug

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

It'd also be great if it were implemented for transferFrom too

If we know a use case already we should try to find a standard for it.

At Augur we have a use case here: https://www.reddit.com/r/ethereum/comments/3peeyp/subcurrency_functions/

Of course, you can do it without a standard (although it's a bit convoluted) as proposed here: https://www.reddit.com/r/ethereum/comments/3peeyp/subcurrency_functions/cw6vcrr

I am 100% sure there are more meaningful uses of prediction market tokens.

Absolutely. Imagine being able to use shares in one market as the currency in another (of course, you can do this, or pretty much any of this stuff in a convoluted manner, but a standard would allow for a much simpler implementation).

+1 to adding that identifier to the standard. It's an incredibly simple addition yet would be quite beneficial for us at Augur, as well as, it sounds like, Gnosis. I imagine there are other dapps that haven't been invented yet that would find this useful as well. i.e. I suspect we're just the first two to have run into wanting this :)

@simondlr

I'm neutral on identifiers, perhaps leaning slightly more to the side of NOT including them in this standard.

I'd like to give some reasons against identifiers:

  1. It's premature optimisation. The current gas limit is indeed a visible issue wrt this, especially in prediction market scenarios where you would want to create multiple token types. And it would not then fit into the current gas limit. You could save significant gas costs by 1) using libraries & 2) using a proxy per token type (that follows non-identifier standards). It's not cheaper than identifiers. But should be cheaper than creating a contract per token. In the future with scaling & potentially cheaper gas costs, it would make sense again to separate concerns neatly & have a contract per token without sacrificing too much. @vbuterin can add on this, if this is reasonable.

  2. What's the default if there's only one token per contract? If token.transfer(to, value) is called without identifier, does it then forward it internally to token.transfer(to, 0, value)? Where '0' is the default identifier? Should it perhaps be the contract address? Should non-identifier calls even be possible? Should other dapps know what address & identifier to call? Why should one token contracts "pay extra" for this? Which leads onto the next point:

  3. If we are talking about saving gas costs: for tokens that are supposed to last indefinitely (company shares, personal coins, etc), then over time, if identifiers are the standard, it will cost more even if the token is referenced by an identifier. An extra few amounts of gas will add up over time. This cost will be shoved to the users.

@Georgi87
  1. using libraries & 2) using a proxy per token type

We have already shown that it is not possible to save gas significantly using libraries in this scenario, because a wrapper function has to be added for each token function in the library. One contract per token type has to be added or am I missing something? Solidity may add features in the future to reduce gas costs further though.

I am now in favor of using one signature: token.transfer(to, 0, value) with default identifier 0. I would put the identifier at the last position. Maybe solidity will offer default parameters in the future and token contracts not using the identifier could just omit it.

How much gas does a lookup in a hashmap actually cost? I guess it is super cheap.

How can we come to a conclusion? Should we do a voting?

@joeykrug

How much gas does a lookup in a hashmap actually cost?

50 gas or at current ether prices ~$2.415e-06

@nmushegian

I'm leaning against multi-asset. If you have a multi-asset controller, and you want its tokens to appear in a simple wallet, you can make it into a factory that will generate a controller contract per symbol on demand.

@ethers
Member
ethers commented Nov 24, 2015

Anyone who wants identifiers, want to draft the EIP (proposal for Multi-Token Standard) on it? By seeing it, it may influence / lead to whether identifiers should be included in this EIP20.

@niran
niran commented Nov 24, 2015

Here's a potential middle ground. We encourage token authors to code to this standard without identifiers. We encourage client contract authors to code to a forthcoming token system EIP that includes identifiers. Simple tokens can be used via the token system API by using a token system proxy that looks like this sketch:

contract ProxyTokenSystem is TokenSystem {
​
  // Copy the approval logic from StandardToken, but store approvals by token
  // address as well. Dapps approve TokenIdentifierProxy for unlimited amounts
  // for the tokens that are used, so the proxy adds its own access control
  // for transferFrom.
  mapping (address => mapping (address => mapping (address => uint256))) approved;
​
  event Approval(address indexed _tokenAddress, address indexed _address, address indexed _spender, uint256 _value);

  function transfer(address _to, uint256 _value, uint256 _tokenAddressInt) returns (bool _success) {
    TokenContract Token = TokenContract(address(_tokenAddressInt));
    Token.transferFrom(msg.sender, _to, _value);
  }
​
  function transferFrom(address _from, address _to, uint256 _value, uint256 _tokenAddressInt) returns (bool _success) {
    address _tokenAddress = address(_tokenAddressInt);
    // Take the body of StandardToken.transferFrom, but add _tokenAddress as the
    // first key to look up in each mapping.
    ...
  }
​
  function approve(address _spender, uint256 _value, uint256 _tokenAddressInt) returns (bool success) {
    address _tokenAddress = address(_tokenAddressInt);
    // Take the body of StandardToken.approve, but add _tokenAddress as the
    // first key to look up in each mapping.
    approved[_tokenAddress][msg.sender][_address] = _value;
    Approval(_tokenAddress, msg.sender, _address, _value);
    return true;
  }
​
  // allowance, unapprove, balanceOf, and totalSupply should be straightforward.
​
}
​
contract FakeEtherEx {
​
  mapping (address => mapping (uint256 => mapping (address => uint256))) balances;
​
  // Before calling deposit, the dapp client will send a transaction approving
  // FakeEtherEx to transfer from the token system. If the token system is the
  // proxy, it will send another transaction to approve ProxyTokenSystem for
  // unlimited amounts of the underlying token if the user has never done so.
  // Alternatively, new simple tokens can whitelist the ProxyTokenSystem after
  // it has been vetted and published to the chain. The dapp can then deposit
  // successfully.
  function deposit(uint256 _value, address _tokenSystemAddress, uint256 _tokenIdentifier) {
    TokenSystemContract TokenSystem = TokenSystem(_tokenSystemAddress);
    TokenSystem.transferFrom(msg.sender, address(this), _value, _tokenIdentifier);
    balances[_tokenSystemAddress][_tokenIdentifier][msg.sender] += _value;
  }
}

Tokens within systems would be addressable by (system address, identifier). Tokens outside of systems would be addressable by (proxy system address, token address). Client contracts would only need to care about one universal API. Token authors would only need to care about one simple API.

This only has a benefit if we succeed in convincing client contracts to use the token system API, not this API. If there's consensus on this, we can bake this strong recommendation into both EIPs. We'll also want to recommend that token authors whitelist the proxy contract once it's published so users don't have to pay to approve the proxy contract.

@frozeman
Member

@nmushegian i agree here. The problem with the multiple tokens is that any wallet etc. will anyway have a hard time displaying these tokens, as it would need to know which tokens exist in this contract (identifiers), so this would create a new problem. Do we put those then in the registry or not, then the registry has to adopt as well.

I think adding a proxy contract layer for this type of custom multi token contracts should work fine, and then any wallet only need to add the proxy contract (where you also can clearly define which token from the market to use) @Georgi87 is that ok?

@simondlr

If we support both standards, then it means that we have overhead in dapps (requiring most developer to write code to support both).

A work-around is to have proxies on either sides. Either from simple token to multi-token (or vice versa). @niran's proposal is such that the dapp authors only write with identifiers in mind, but then have a proxy (one proxy router for ALL). This does have a small overhead/trade-off. Requiring simple tokens to approve the proxy. And then the proxy should be added to dapps (not the actual token contract) (along with keeping both proxy & token address saved).

My gut feel is that we should just have 2 separate standards, and let token creators canvas dapp creators to add them. If wallets & exchanges don't want to add multi-token support, then there will be others who swoop in to add it. If they don't, then for people like Gnosis & Augur, whose tokens (usually) have a limited shelf-life, they can (hopefully) switch out to another system in the future (pending scaling changes).

Currently, however, Gnosis & Augur WILL be creating plenty of tokens and currently have a visible problem wrt gas limits. Using identifiers is pretty much required. Using proxies to interface with the rest of the ecosystem will cost more, which is a trade-off.

There's plenty of trade-offs to consider here.

  1. Usability from a user's perspective.
  2. Cost to developers.
  3. Actual network constraints.
  4. Future proofing the design.

What's the most important considerations. Ultimately, we should cater to number 1. As developers we should probably cover costs for proxies in the face of network constraints.

Having an exchange or wallet where a user needs to also specify an identifier OR requiring a user to themselves authorise a proxy ("What's a proxy?") is worse UX.

Thoughts?

@frozeman
Member

I think two standards is the way to go. I want the API's to be simple and clean. All this proxy stuff sounds already way to complicated, for both the developer and users.

@Smithgift

I may as well put in my two $TOKEN_UNITS in, re: approve/cheques/allowance.

It's possible to atomically transfer tokens with a wallet contract.

  1. The DApp-to-be-paid has two address, a collection address and a holding address.
  2. The wallet contract transfers the token to the collection address
  3. The DApp-to-be-paid moves the token from the collection address to the holding address, and then does its stuff.

Should something break along the line, the whole transaction is reverted, and there isn't a cheque lying around needing to be canceled. Of course, this does require wallet contracts and two-address systems, which are not so simple.

I do think cheques as distinct, transferable (sub)tokens have their advantage with more complex tokens. Say something like Freicoin, and the coins only decrease after the cheque is cashed.

EDIT: And you could use the exact same system above by having an approval last only for the transaction. You still need a wallet contract, though.

@nmushegian

I do think cheques as distinct, transferable (sub)tokens have their advantage with more complex tokens. Say something like Freicoin, and the coins only decrease after the cheque is cashed.

EDIT: And you could use the exact same system above by having an approval last only for the transaction. You still need a wallet contract, though.

These two use cases are what led to work with 'withdrawal'-based delegation instead of approval-based. In this system any contract can withdrawn "to" the transaction, and any unused funds an the end of the transaction are (virtually) refunded to "the session initiatior". I think cheques achieve the same goals in a cleaner way.

@nmushegian

Cheques and multi-token controllers both can be implemented as "standard extensions", they could work seamlessly with simpler controller contracts, especially if you keep your data separated or have a admin action "backdoor"

@niran
niran commented Nov 25, 2015

Having an exchange or wallet where a user needs to also specify an identifier OR requiring a user to themselves authorise a proxy ("What's a proxy?") is worse UX.

What's a token address? No sane UI will expose these details to a user when you can just search a token registry by name or check a subset of all tokens for balances to display. Similarly, the proxy approval is either unnecessary because the token author followed our advice and whitelisted it, invisible because the user is using a normal browser with local or remote RPC, or par for the course in Mist and Metamask since every transaction must be approved and users won't understand most of them.

Coding to the TokenSystem API doesn't make anything harder for dapp developers. Finding identifiers isn't a problem because they will use a token system registry that includes identifiers. The only changes from the status quo are treating token system registries as the primary registries instead of token registries, adding an extra argument to the call, and sending an approval transaction if the proxy doesn't have an allowance for the token in use. This is not difficult.

Coding to the Token API makes things harder for token system developers by forcing them to find a way to publish token contracts for each of their tokens. This is actually difficult! None of the other problems we've discussed are.

Anyway, I think steering dapp developers to the TokenSystem API while allowing token developers to use this ERC's Token API is the better approach, but there are suboptimal workarounds if we tell everyone to code to the Token API instead. The functionality will be the same at the end of the day.

I think the only other issue left was decimals. Let's remove that in favor of registries and move forward with this.

@christianlundkvist

I vote for skipping decimals in this low-level standard. Decimals are a higher-level abstraction IMO.

@nmushegian

@christianlundkvist agreed, the need for decimals shows itself either when you are creating a GUI or when you are dealing with prices, in both cases you have to define a registry since you have more than one token anyway.

@ethers
Member
ethers commented Nov 25, 2015

I've filled your name in at the poll:
https://github.com/ethereum/wiki/wiki/Poll-for-token-proposal-EIP-20

If I got your "position" wrong, feel free to update ;)

@nmushegian

small naming improvement: in allowance context, change (address, spender) to (owner, spender)

@nmushegian

Just to be clear, is the argument to Approvel supposed to be the latest total allowance? Not individual approval calls, which would require you to scan events to determine the latest approval, and doesn't have anything you could use for unapprove

@ethers
Member
ethers commented Nov 26, 2015

@nmushegian: for approve, are you asking about the value argument? it's not the latest total allowance, it gets added to any existing allowance:
"If this function is called twice it would add value which _spender is approved for."
I see something less clear about unapprove as you mention...

@frozeman: for unapprove I think this part should be deleted "Must reset all approved values." since "all approved values" is not as clear.

@nmushegian

@ethers No, I'm asking about the Approval event. When you add value to the approved amount via approve, do you emit Approval(value) or Approval(allowance(...)) ?

@frozeman
Member

You would emit the current added value, as you got before Approval events for the previous allowances. So you can scan the history to see all the approval events.

@frozeman
Member

We could add a _value to this method:

function unapprove(address _spender) returns (bool success)

But this would then be tedious to track whats is still the left allowance and could end up being unclear. E.g. spender has still 132 token units left, but you unapprove then for 200, will it go negative :)

There is thinks its better to leave it as is.

@simondlr

Yeah @frozeman. I'm wondering there are scenarios where you would want to only remove increments? Either you could just unapprove and re-approve for the correct amount?

@niran Do you mean that a token author explicitly, hardcodes a proxy so that individual accounts don't have to approve it?

@ethers
Member
ethers commented Nov 26, 2015

@nmushegian: got you, for some reason was reading what you wrote as Approve1 instead of Approval.

@frozeman: for Approval event, I suggest we should update the documentation to make it clearer, something like: "Triggered when _owner approves _spender to direct debit an additional _value from its account." Also, I think "withdraw" is simpler terminology than "direct debit".
Another suggestion is: "Triggered whenever an _owner calls approve(address _spender, uint256 _value)."
For unapprove I see the description was updated to "This will disallow all before approved values reset." That's even more unclear. Suggestion: "This will reset the allowance of _spender to zero."

I agree we don't want unapprove to get more complicated by adding a _value.

@frozeman
Member

I updated according to you suggestions

@niran
niran commented Nov 26, 2015

@simondlr Yes. It's equivalent to telling token authors to all use the TokenSystem API, but by approving the deployed proxy and considering it part of their codebase, they don't have to.

@nmushegian

@frozeman then how do you detect an Approval(0) emitted by an unapprove() vs an approve(0)? You either need to add a new event, change the semantics, or assume you can track "events" by function signature...

@nmushegian

If the Approval event emitted the last total approved value you can infer all you need. It might be named LatesetAllowance.

@simondlr

@nmushegian good point!

Currently, would vote for your option. LatestAllowance(address indexed _owner, address indexed _spender, uint256 _totalAllowed).

Only potential issue is that you need to go back and find the previous event to figure out how much it increased. If your dapp does this call you'll have that info around. But if a tx not in your control does it, your dapp will need to go back and look when the previous event was. Another option is to when the event is fired, get/keep the allowance around just from before it was fired. Not entirely sure how resource expensive these options are.

Perhaps there's another option?

@janx
Member
janx commented Nov 28, 2015

I suggest keep Approval and add new Unapproval event, which is more clear and simple mapping to functions.

@ethers
Member
ethers commented Nov 28, 2015

+1: keep Approval and add new Unapproval event

@caktux
caktux commented Nov 28, 2015

We really don't need an Unapproval event, it is simply an Approval event of 0. And the Approval event should emit the value of the approve call, not the new total approved value; that's what allowance returns.

@nmushegian

@caktux Then we are missing an event to detect when allowance changes to 0 - the point is to avoid having to call allowance() each block. See discussion above.

@caktux
caktux commented Nov 29, 2015

@nmushegian Read the discussion twice and thought it through before answering, thank you. Maybe you should have done the same with my reply. But let me explain it more clearly. There would never be a need to call approve(0) in the first place; a dApp would only be wasting a transaction (or gas if part of another call) doing so. Therefore an Approval event of 0 is an unapproval. Remember that events are logs, not return values themselves, and a dApp should not base important actions on the values of events (think of chain reorgs) but rather use them to list previous actions or calls, and to trigger new calls to validate results and take further actions if needed. Back to the point, you would not have to call allowance each block, but only after detecting an Approval event. And it needs to return the approved value (not the new total) for the sake of granularity and to prevent the issue described by @simondlr. Calling allowance after detecting the event (and again, obviously not at every block) is both cheap and the logical way to get the newly allowed total or to confirm an unapproval.

@ethers
Member
ethers commented Nov 29, 2015

There would never be a need to call approve(0) in the first place; a dApp would only be wasting a transaction (or gas if part of another call) doing so.

Makes sense. But could approve(_spender, 0) be used by a malicious dApp E to confuse another dApp F, that _spender has been unapproved?
Seems things are fine as long as dApps such as F follow best practices as mentioned in these important points:
"Remember that events are logs, not return values themselves, and a dApp should not base important actions on the values of events"
"Calling allowance after detecting the event", before taking an action, is a requirement and not optional.

@caktux
caktux commented Nov 29, 2015

We shouldn't add events unnecessarily and bloat the standard because of possible dApp Fs; it wouldn't be doing them a favor either to make it easier to use event values directly and implying it's the correct way to use events when it's not. "Calling allowance after detecting the event" would only be required if taking any further action, basic good practice, but not if you're only listing past approvals, in which case you'd watch or filter on past events (including the one from that pesky approve(0)) and call allowance only once to show the latest total.

@nmushegian

I thought the point of logging was you can take a minimal set of logs plus proof they came from the chain, and rebuild your dapp's off-chain state. You're saying I need to rescan the chain any time I rebuild any off-chain databases.

but not if you're only listing past approvals, in which case you'd watch or filter on past events

But not if you want to list past allowances or actually list past approvals unambiguously, in which case you have to get the whole history and do a scan from one end or the other.

What are events supposed to be for then and why do we need an approval event at all? Are the use cases just "get alerts when you need to call the chain to update an approval value" and "list past approval-like events"?

use event values directly

This is incidental, probably the ideal solution would be for the event to record the diff, not the value. Too bad we've used up all the bits in the value arg. Anybody think adding bool is_negative to Approval would be less offensive than adding a separate event?

@simondlr

Therefore an Approval event of 0 is an unapproval.

I feel this is still too ambiguous. All token implementations must then put in functionality to disallow approvals of zero in the approve() function. Wouldn't these extra checks not cost more in gas than just having a different event?

I'd still vote for keeping approve as it is currently: only the recent approval (not the total) and have a separate event for `event Unapproval(address indexed _owner, address indexed _spender)``.

@nmushegian

Can we take a short detour to address the VM error check issue: Functions for which 0 is a valid return value should contain an extra bool _success argument, or there needs to be a separate discussion about a better convention for handling these edge cases.

function balanceOf(address _who) returns (uint _balance, bool _success);
function allowance(address _owner, address _spender) returns (uint _allowance, bool _success);

This became a high priority for me again because I learned we can't implement the pattern we like (throw vs extra success argument) and then retrofit the more common one because Solidity won't let you define both:
ethereum/solidity#259

@chriseth
Contributor

@nmushegian there are several other ways to distinguish functions apart from their return value types. You can e.g. change the name to indicate whether it will throw or not and you can also add another parameter (bool _throwOnError).

And by the way, function overload resolution does not only apply to Solidity, but to any framework that uses the ABI. This includes serpent but also web3.js: Currently, web3.js does not even support function overloads in general.

@alexvandesande

Suggestion: should we move from uint256 to int256? There are some occasions that a token might want to allow the creation of a debt and I don't see why that should be prevented in the standard. The only downside is that the maximum balance allowed will be halved, but considering the balance allowed by 256 bits is measured in the uncountable quattuorvigintillions, I doubt it would be a problem.

@simondlr

@frozeman last event is missing a "t". :)

@innovator256

Thank you all for your discussion I usually just watch/study from a distance, forgive the last minute interjection, but I feel as though it is time to speak up on a particular issue being over looked.

Question: Why is decimal / percentage representation a lower order priority?

In my humble opininon this is one of the important use cases partaining to token manipulation...some resulting personal paralysis as to how to implement in that area...

The Base Unit specification is FAR from clear and gives room to isometric misrepresentation between clients and contract, not to talk of dfferent incoherent client implementations that might not follow standards ...

What if I have a base unit of 3 of total units of 100? how is the fraction represented in contract? 33.3333 does it mean that I am now to normalize the representation to allow users send fractions of 33.3333 ? How would you represent more granular decimals in the contract as a result of user to user interactions?

Maybe I am confused, in anycase I am just asking about the state of consensus on this particular issue that seems overlooked, someone please shed light about the direction / conclusion on this before the gavel comes down. Thanks for all your hard work!

@simondlr
simondlr commented Dec 1, 2015

Hey @innovator256. It's because not all token systems will deal with decimals. Even if it was, it is still being decided where registry information like this should live (which will probably be discussed in a future EIP).

@innovator256

@simondlr Thank you I understand, not all require decimals, but a vast majority most probably will even if its not apparent right now...So is the verdict that decimal is a client display issue only? How do contracts deal with fractions from the client since there are no floating point numbers yet?

@simondlr
simondlr commented Dec 1, 2015

@innovator256 It could be in client display only, in the contract itself, or in a registry. It's just not ubiquitous enough that it warrants to be in the low-level standard. :)

How do contracts deal with fractions from the client since there are no floating point numbers yet?

There are some work-arounds, like having a multiplier and then dividing. Your granularity is based on how high you want the multiplier to be. Clients then have to know how to interpret it as well (knowing what the multiplier is). This is a basic hack I used in Ujo's pricefeed (for example).

@niran
niran commented Dec 1, 2015

A token contract should never be provided a fractional value. Tokens aren't infinitely divisible. Their contracts represent the smallest unit of the token. A dollar contract would track cents. A Bitcoin contract would track satoshis. An Ether contract would track wei. Fractional cents, satoshis and wei don't exist. Clients can use fractional cents as an accounting fiction that they resolve somehow, but the contracts only know about whole units.

@innovator256

@simondlr Got ya, I guess that works, thought about similar scheme just didnt know if there was some acceptable official standard we should be aiming for or just got confused. Either way thanks, Ujo's very cool by the way!

@innovator256

@niran true by today standards, but by definition contracts can be anything you define them to be, they are limited right now due to design decisions in terms of whole units, this may very well change in the future...

@nmushegian

What if I have a base unit of 3 of total units of 100? how is the fraction represented in contract? 33.3333

You're saying "if I use a token with low resolution it will show its discrete behavior in common scenarios". Ethers have finite granularity too and somehow we talk about 1/3rd of an ETH, 0.9 USD/ETH, etc.

@kumavis
Member
kumavis commented Apr 13, 2016

then i think in support of the "several live deployments", we should declare v1 finalized and begin v2 discussion

@alexvandesande

Agree On finalizing this and figuring out a versioning manner. Maybe add a variable "standard"?

On Apr 12, 2016, at 16:47, Nikolai Mushegian notifications@github.com wrote:

How about this: Does anyone have any reason to change the behavior of any of these functions which could NOT be handled by an optional extension and/or more powerful user "wallet" proxy contracts?

If nobody can answer "yes" for N days then this should be finalized.


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub

@ethers
Member
ethers commented Apr 16, 2016

I think it should be written according to the process like:
https://github.com/ethereum/EIPs/blob/master/eip-X.mediawiki
Especially note the implementation section, so that we can figure out more what the "live deployments" are.
Tokens V1 is probably done when there's a PR that's merged to this EIP repo, just like the existing EIPs.

PS: @GriffGreen @LefterisJP your feedback might still be in time. If you also have your proposal written down in EIP format and show your implementations in that document, it would be easier for community to evaluate.

@GriffGreen

@ethers :-) Glad to hear we still have time to inspire a change to this name. If the Ethereum Wallet allowed us to name the functions whatever we want (I'm sure customizing the UI will be available eventually), it wouldn't be a big deal. Until then it creates a difficult user experience.

Thanks for the recommendation, is this the format that you are talking about? https://github.com/ethereum/EIPs/blob/master/eip-X.mediawiki

@kumavis
Member
kumavis commented Apr 18, 2016

can we collect the live deployments here? address and abi and any other data

@flyswatter flyswatter referenced this issue in ConsenSys/truffle Apr 19, 2016
Open

Make sample coin compatible with EIP20 #140

@ethers
Member
ethers commented Apr 19, 2016

is this the format that you are talking about? https://github.com/ethereum/EIPs/blob/master/eip-X.mediawiki

@GriffGreen yes. The "process" is also supposed to be https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1.mediawiki

@GriffGreen

We have many on the testnet (Morden), but here is the one that had a lot of testing and confused people:
ptDAO address: 0xad23d7c443382333543dd13c3b77b99b2a7e2c6d
JSON: https://slockit.slack.com/files/lefteris/F0Y1YJGV7/full_dao_json.js

For more context see this blog post: https://blog.slock.it/take-the-first-steps-toward-becoming-a-dao-ninja-and-win-free-eth-1c795fa3d94e#.8zdje9yfa

@alexvandesande
alexvandesande commented Apr 19, 2016 edited

How does your deployed tokens compare with http://ethereum.org/token?

@LefterisJP
Contributor

@alexvandesande At the moment we use the standard as defined here in this EIP.
https://github.com/slockit/DAO/blob/master/Token.sol

Griff's experience showed that people using these functions were confused by the names.

@nmushegian

mainnet MKR: 0xc66ea802717bfb9833400264dd12c2bceaa34a6d
tentative mainnet dai: 0xd3a84329b273d5b63002cf390a736c2f204b1aeb

entrypoint source: https://github.com/nexusdev/dappsys/blob/audit/contracts/token/frontend.sol

@kumavis
Member
kumavis commented Apr 20, 2016

didn't see the coin from the dapp-bin linked yet so including it here https://github.com/ethereum/dapp-bin/blob/master/standardized_contract_apis/currency.sol

@ethers
Member
ethers commented Apr 21, 2016

@kumavis by #20 (comment) thought you meant the live implementations of this EIP 20 ;) But there could be value in seeing other implementations as you linked to dapp-bin.

@edvail
edvail commented May 27, 2016

Hi,

When I try to create a new instace of the standard token, compiler throws an error:
Error: Trying to create an instance of an abstract contract. new token()

I suppose it is due to abstract receiveApproval function specified in standard token:
contract tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData); }

if I'm right, then how to deploy standard token contract from within another contract?

@pipermerriam

This isn't the place for those types of questions. I suggest you ask on the
ethereum stack exchange site.

On Fri, May 27, 2016, 2:52 PM Ed Ilyin notifications@github.com wrote:

Hi,

When I try to create a new instace of the standard token
https://ethereum.org/token, compiler throws an error:
Error: Trying to create an instance of an abstract contract. new token()

I suppose it is due to abstract receiveApproval function specified in standard
token https://ethereum.org/token:
contract tokenRecipient { function receiveApproval(address _from, uint256
_value, address _token, bytes _extraData); }

if I'm right, then how to deploy standard token contract from within
another contract?


You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
#20 (comment), or mute
the thread
https://github.com/notifications/unsubscribe/AAyTgmy7MvEkhcWtd_YOQXth9GLBPQVmks5qF1mZgaJpZM4GlZ6N
.

@nmushegian nmushegian referenced this issue in kvhnuke/etherwallet May 28, 2016
Closed

Generic ERC20 token support #49

@nejucomo

Hello all. I just read over the standard and immediately did not understand the approval API, and I still don't after reading about 20% of the comments in this (very long) thread. It may be because I'm missing some fundamental understanding of Ethereum.

If I want to delegate some sub-authorities to some contract A, isn't the whole point of composable software and contracts that I can define how I want to do that delegation with a new contract B? In this case, if A is a token or currency contract, there are many, many possible B's I might want, but you might not want some that I want, and you might want different ones. So let's leave all of those out of A! Here are some B policies I might want:

  • an account with A is controlled by a DAO.
  • an account administrator can hand define allowances for an arbitrary number of other addresses, but can adjust those allowances at any time before the "allowance users" spend the funds.
  • like the last bullet, except the total allowances cannot exceed the account balance on A so that all allowances are "fully backed".
  • like bullet 2, except each allowance may be up to the full account on A, so all allowance controllers must rely on each other.
  • like bullet 2 except every transfer must sit in a pending state for 1 week during which time any of a set of auditor addresses can cancel the transfer,
  • etc... etc...

My point here is that there's no way to put any combination of those potential features directly into A, and much less a standard for all A's. To do so may enable some use cases, but will hobble the greater long term ecosystem.

I am in the camp of "many decomposed EIPs / interfaces", so I want to see an extremely minimal definition of what a token is that wallets can transmit. By minimal and decomposable, I mean only the transfer and balanceOf methods and the Transfer event.

It's clear how wallets can represent these: the token is uniquely identified by its Ethereum address (and not a human-friendly name that requires some authority, at which point there will be a political struggle for dominance that will play out in wallet development communities). Let's not recreate the mistakes of DNS and Certificate Authorities, as that would obviate the primary value of a decentralized protocol!

@Smithgift

@nejucomo: approve as I understand it, is more of a low-level function than a high-level system for allowances and whatnot.

If there's just transfer, then there's no way any other address can send tokens than the owner, no matter for what scheme it is used. At best, a token holder must manually write a contract with whatever features, and if he needs another, he has to transfer all his tokens to a new contract.

Whereas, with approve, if he decides he needs some feature, he can approve a contract supporting that feature, without having to alter his wallet's code. If needs to allow an exchange access to his tokens for example, he can approve the exchange contract (after reviewing the code, if he pleases) and then deapprove it afterwards. In fact, he can do this in the very same transaction.

@niran
niran commented May 29, 2016

@nejucomo There is no punishment for omitting approval from any token you write. This "standard" is the best place to start for interoperable tokens, but contract authors can do whatever they want. Tokens without approval will be unusable in contract systems that use your funds, like financial markets. If you want to discuss approval further, let's do it on the Ethereum Stack Exchange rather than here. This "standard" is done.

@Arachnid
Contributor

Could this be published as an EIP in the repo, rather than as an issue? A huge long comment thread isn't the easiest way to understand a protocol.

@aakilfernandes
aakilfernandes commented Jun 21, 2016 edited

I'd like to remove the returns (bool success) from the various functions and simply throw if it fails.

When calling functions with web3, there is no way to get the result of a non-constant function and therefore no way of telling if a call was a success.

This will also have the additional benefit of protecting protecting devs who fail to consider that certain actions may fail. It is unintutive, for example, that approve could fail

@aakilfernandes

How important are variable names to the standard? Is a contract still compliant if it uses different variable names?

@nmushegian

@aakilfernandes If you mean argument and return value names: those don't matter at all.

@aakilfernandes

@nmushegian yup, thats what i was referring to

@nmushegian
nmushegian commented Jun 25, 2016 edited

I'd like to remove the returns (bool success) from the various functions and simply throw if it fails.

I favor this approach as well (return true on success and throw on failure). As an aside, return types are not encoded in the ABI at all. Return values are also currently swallowed by .call in solidity.

@simondlr
simondlr commented Jun 26, 2016 edited

I'd like to remove the returns (bool success) from the various functions and simply throw if it fails.

Similarly asked this question a while back on Reddit (https://www.reddit.com/r/ethereum/comments/3qej72/lets_move_to_throw_vs_returns_bool_success_as/). Times have changed and so might necessitate looking at this again. It was also because I misunderstood throw at that point in time. :)

throw uses up all the gas and returns OOG exception, and if it is a Contract call (as opposed to raw call), then exceptions are rethrown.

So, the question: Are there use cases where one would want to do a Contract call, but expect a potential failure and not want to the whole transaction to revert? ie: "Try Token.transfer(). If true, continue. If false, do something else." Not sure, but seems like there could be?

atm, until EVM changes are implemented, it is also not possible to differentiate between a true OOG vs a throw's OOG. So you can't tell the user why it failed. It's either: there was something wrong with your transfer() function vs you didn't supply enough gas.

When calling functions with web3, there is no way to get the result of a non-constant function and therefore no way of telling if a call was a success.

Do an eth_call beforehand? eth_call returns the return values vs a transaction which would return the tx hash. Similarly, if a dapp wanted to check if a transferFrom, for example, would pass, if a throw is used then the dapp front-end would similarly not know if enough gas supplied or not, vs a true fault (they weren't allowed to do so).

It is unintutive, for example, that approve could fail

It depends how you implement your standard for whatever reason. There could be scenarios where, for example, you can only approve a whitelisted set of dapps/contracts.

Return values are also currently swallowed by .call in solidity.

This seems like the only reason to me to switch to throw. But you can use abstract contracts to get the right return values (if using Solidity). This might not be possible in all scenarios (especially if you manually craft function signatures) and expect to not have an abstract contract for EVERY possible use case.

My personal feeling is that until throw changes its behaviour in the next EVM upgrade (to not do OOG), we should keep it as is. And revisit it then.

It's a trade-off. The issues described seems possible to be mitigated vs the other option where you can't work around it. You can use eth_calls, and abstract contracts (somewhat) to determine if it succeeds or not. The other option seems to have no work-arounds (not knowing the real reason why a transaction failed). Unless I'm missing something?

How important are variable names to the standard? Is a contract still compliant if it uses different variable names?

It's vanity. However. Mist-created tokens & ConsenSys HumandStandardTokens use decimals, symbol & name as additional vanity variables. Allows for interoperability between the wallets. And as a reminder. These vanity vars are for tokens expected to be used by humans and shouldn't be in this specific standard (not all tokens will have names or symbols).

@Arachnid
Contributor

throw uses up all the gas and returns OOG exception, and if it is a Contract call (as opposed to raw call), then exceptions are rethrown.

Not quite - if you call using .call(), you can specify a gas limit, so going OOG won't cause the caller to OOG too.

Do an eth_call beforehand? eth_call returns the return values vs a transaction which would return the tx hash.

This introduces race conditions, as well as the possibility that the function depends on something that differs between the call and the transaction.

Similarly, if a dapp wanted to check if a transferFrom, for example, would pass, if a throw is used then the dapp front-end would similarly not know if enough gas supplied or not, vs a true fault (they weren't allowed to do so).

This isn't great, but being able to report failure is definitely more important than being able to report the nature of the failure.

@simondlr
simondlr commented Jun 26, 2016 edited

Not quite - if you call using .call(), you can specify a gas limit, so going OOG won't cause the caller to OOG too.

I should've clarified. throw uses up all the gas in the sub-execution, yes. So specifying some gas (using .call) won't make the caller also OOG, yes. But still, the calling contract still would not

This introduces race conditions, as well as the possibility that the function depends on something that differs between the call and the transaction.

Yes. sidenote: Doing eth_call is always prudent anyway, since otherwise you are going to let a user spend gas for nothing if it is going to fail anyway. It depends on the dapp though. Sometimes you will need to issue an tx without knowing what will happen (many states will change in between submitting and inclusion).

being able to report failure

You will still be able to do this in both variations. If you don't rely on eth_call beforehand, you'll need to wait that the tx has been included (with exceptions or not).

  • So if using bool success method, you'll need to issue an eth_call afterwards to see if it succeeded to expectations.
  • If it was thrown, then you'll know (checking tx receipt) without issuing another call. But at the cost of not knowing why it failed.

If the tx succeeded & throw was only done in a sub-execution, then you'll need to do eth_call anyway afterwards. The failure is not being reported if it happened in a sub-execution.

Correct me if I'm wrong.

@Arachnid
Contributor

So if using bool success method, you'll need to issue an eth_call afterwards to see if it succeeded to expectations.

This again introduces race conditions, since other calls may happen in-between.

@simondlr

This again introduces race conditions, since other calls may happen in-between.

By that argument, then eth_call seems pointless since every time you do, the blockchain might already be out of date?

We're forgetting about events, which should be sufficient to know if things happened or not.

So with bool success method & it is supposed to fail, the transaction goes through without an exception but the event is not invoked. If it is in sub-execution, then you'll similarly know, since the event is not invoked.

With throw & it is supposed to fail, the transaction goes through with an exception, and the event is not invoked. But then you don't know why it failed (exception or true OOG). If it fails in a sub-execution, then the event is not invoked either.

If you don't rethrow, then throw anywhere in the code will only work on a first-level tx to give you for sure information that an exception occurred. It seems you can't rely on throw to give you information in the event that the throw occurs in a sub-execution (if you aren't rethrowing).

You'll need to use events or eth_call in both scenarios.

So, throw is only useful if it actually propagates an exception (either rethrowing a sub-execution or a first-level throw). But then you are still stuck with the issue of not knowing why the exception occurred.

@Arachnid
Contributor

By that argument, then eth_call seems pointless since every time you do, the blockchain might already be out of date?

No; eth_call is good for reading data, but absent a mechanism encoded into the API of a given contract, can't be relied on to return the exact same result as a transaction with the same parameters. That is, don't use it for read-modify-write unless you can be certain that nothing will change.

One way to handle this, for instance, would be to allow the sender to specify a unique ID, and store the return value in a map keyed by the sender address and that ID; the sender can then use eth_call to fetch the result after the transaction has been processed. Obviously that doesn't apply here, since it would require changing the token standard.

@nmushegian
nmushegian commented Jun 26, 2016 edited

@simondlr @Arachnid Would this argument be happening if said there were already a way to extract error reasons out of stack traces? eth_call gives you a tiny fraction of the information you have about what will (probably) happen, even in a thin client...

function throws(string reason) { log(reason); throw; } The event doesn't need to disappear into the ether (har har) even though it doesn't get committed, I mean, those reason string bits clearly exist in your computer's memory at some point, right?

I think that the advantages of having the exception mechanism manage state rollback FAR outweighs the fact that it makes determining error causes harder.

@Arachnid
Contributor

I think discussions about improving the EVM are offtopic for this ERC; I was simply trying to clarify the behaviour and avoid dependence on patterns that are subject to race conditions.

@nmushegian
nmushegian commented Jun 26, 2016 edited

My point was that it's NOT an EVM improvement! You can already extract error reasons out of transactions with exceptions and/or local eth_calls if you try hard enough. That should totally invalidate a major argument against using exceptions you were going back and forth on, no?

(edit: I suppose you can think of it as an "evm improvement" if you also classify estimateGas or similar as an evm improvement)

@Arachnid
Contributor

Fine;, client improvement if you wish; it's still entirely off topic for this etc.

@aakilfernandes

Similarly, if a dapp wanted to check if a transferFrom, for example, would pass, if a throw is used then the dapp front-end would similarly not know if enough gas supplied or not, vs a true fault (they weren't allowed to do so).

@simondlr you can check the estimatedGas. If its some ridiculously high number (i forgot the exact number, but its higher than the block gas limit) you can be sure its a throw and not simply an oog.

@nmushegian
nmushegian commented Jun 26, 2016 edited

Fine;, client improvement if you wish; it's still entirely off topic for this etc.

?? Is throw vs error codes off topic too? You guys were talking about whether eth_call gave you enough info for error checking b/c it relates to this ERC, which is a totally arbitrary client feature.

On further thought discussing theoretical client-side features seems extremely relevant when we are creating on-chain standards. Willing to bet future token implementors will ignore the return values and point to this as an example of standards working around lack of tooling.

@Arachnid
Contributor

?? Is throw vs error codes off topic too? You guys were talking about whether eth_call gave you enough info for error checking b/c it relates to this ERC, which is a totally arbitrary client feature.

Anything that requires client modifications to function is out-of-scope; despite nominal 'draft' status, this EIP is already implemented by a number of high-profile applications, so any changes need to be backwards-compatible, too. If you've got specific client enhancements in mind, please do submit your own EIP, or open an issue to discuss the changes; I'm certainly in agreement that there's things we can do to improve the tooling.

@cassiopaia cassiopaia referenced this issue in makerdao/maker-market Jun 28, 2016
Closed

Variable precision / DGD support #23

@aakilfernandes

@alexvandesande @simondlr regarding hooks:

We're looking to turn eth into a standard token contract for serenity. In which case, it will need to support hooks since a large number of contracts already have a "on ether transfer" function in the form of default functions. In which case, since we have hooks for eth, it would be in line with standardization to have hooks for all tokens.

I can easily see a DAO that needs to keep track of when it received tokens for bookeeping purposes (ie funds received in Q2 do X, while funds received in Q3 do Y).

@sepehrmohamadi
sepehrmohamadi commented Jul 14, 2016 edited

What is a complete example of this standard?
I have seen some examples:

https://ethereum.org/token
https://gist.github.com/alexvandesande/0d1a998d949e26942212
https://github.com/ConsenSys/Tokens

but non of them 100% compatible with standard!
For example non of them included totalSupply function!

@mtbitcoin

having a method to return the divisor/decimal values of the token value as part of the standard would be helpful

@simondlr

having a method to return the divisor/decimal values of the token value as part of the standard would be helpful

Some tokens won't have decimals/divisors, thus it is not part of the standard. However, Mist & others token have implemented public vars such as:

symbol
name
decimals

which can be used to retrieve this.

@chfast
chfast commented Sep 28, 2016

Can someone produce up-to-date version in the first comment?

@haiqu haiqu referenced this issue in bitsquare/bitsquare Oct 2, 2016
Closed

Support for ETH tokens required #620

@maraoz
maraoz commented Oct 13, 2016

Is there any consensus on the throw vs return false; discussion? I tend to favor the throw approach.

@simondlr

@maraoz there doesn't seem to be a consensus on the throw vs return false. Most tokens employ return false atm. In the future, it's likely other opcodes & changes will give more granularity to exceptions (why it happens and not reverting the whole tx), and in that case, it's likely there will be a more unanimous consensus to employ throw vs return false (in not just token contracts).

@chfast the top comment has the current ERC20 standard. For the vanity variables, there isn't currently any defined ERC for it, except from organic adoption. Might be useful to include the three vanity vars in the top comment as optionals that have been adopted/used, namely: symbol, name & decimals. approveAndCall() & receiveApproval() might require its own agreed upon ERC.

@joeykrug

I use throw as well @maraoz

@maraoz
maraoz commented Oct 14, 2016 edited

I also propose changing the function totalSupply() constant returns (uint256 supply) for a simpler uint public totalSupply;

@paulperegud

It might be a good idea to have a deadline for changes in ERC20.

@Georgi87
Georgi87 commented Oct 27, 2016 edited

I am also in favor of using throw instead of return false.

Another thing: As soon as ethereum/interfaces#1 is done, we should also remove return (bool success). Assuming we use throw, this return value is only needed to validate on the frontend that a transaction can be applied. And this becomes obsolete with ethereum/interfaces#1

@Georgi87

And one more: Solidity changed and won't return false in case of .send(0) anymore. Should the standard also allow transfers of 0 value without a throw/false? The current implementation returns false.

@ethers
Member
ethers commented Oct 27, 2016 edited

Because a contract/account can't refuse tokens, transfer(address, 0) should return false imo. [A contract can cause send(0) to fail, so we can't get consistency with transfer and send.]

@Georgi87
Georgi87 commented Oct 27, 2016 edited

A contract can cause send(0) to fail

True, but you can still send ETH to a contract without their control (e.g. using suicide(contract_address)). On EVM level ETH can be moved to any account/contract without executing any code.

@Georgi87

Assuming we would stick to return false: It would be only a temporary solution until there are other errors than OOG allowed and can be handled. Assuming throw is always used, any return parameter becomes obsolete. I think using throw now will allow us to keep the standard and do fewer changes later on keeping it backward compatible.

@frozeman
Member

@chriseth you have any thoughts on that?

@tymat
tymat commented Oct 27, 2016

We're trying to release a standards compliant token before the end of this year and seeing the moving target nature of this topic we should try and create an ERC-20 versioning system. There are already a few existing tokens that are trading on various exchanges which follow the original standard and introducing new changes to ERC-20 at this point in time will create further confusion in the community.

An example versioning would be:

ERC-20 version 1.0 (ERC-20v1)

Perhaps in the future versions we can add an ERCVERSION constant so that contracts can easily switch contexts depending on the token contract they are transacting with. We can assume that ERCVERSION is not available then it's ERC-20v1

@frozeman
Member

Maybe ESTVERSION (Ethereum standard)
I know people using ERC 20 as a default name, but that just means "Ethereum Request for Comment"

@larspensjo

+1 for a versioning system. It is unlikely that we can create a
specification now that works for all future.

On Thu, Oct 27, 2016, 22:41 Fabian Vogelsteller notifications@github.com
wrote:

Maybe ESTVERSION (Ethereum standard)
I know people using ERC 20 as a default name, but that just means
"Ethereum Request for Comment"


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#20 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/ACTPNsXmLNKd5A1Hxe_D4rwRcbDe2rqIks5q4QyCgaJpZM4GlZ6N
.

@larspensjo

There are a couple of ways to do versioning. Using a constant is one. We
could also use some kind of hash or random number. That would support a
decentralized control, where anyone can propose definitions.

We could use a bitfield, representing support for various sub requirements.

There could also be a more general query function. Argument is an enum
representing some functionality, and the function returns a bool signalling
support or not.

On Thu, Oct 27, 2016, 22:46 Lars Pensjö lars.pensjo@gmail.com wrote:

+1 for a versioning system. It is unlikely that we can create a
specification now that works for all future.

On Thu, Oct 27, 2016, 22:41 Fabian Vogelsteller notifications@github.com
wrote:

Maybe ESTVERSION (Ethereum standard)
I know people using ERC 20 as a default name, but that just means
"Ethereum Request for Comment"


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#20 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/ACTPNsXmLNKd5A1Hxe_D4rwRcbDe2rqIks5q4QyCgaJpZM4GlZ6N
.

@ethers
Member
ethers commented Oct 28, 2016

With versioning, we may just want to use whatever the next number is. We may want to avoid/minimize creating processes that different bodies have used and possibly solved.

For example https://tools.ietf.org/html/rfc5321

Request for Comments: 5321
Obsoletes: 2821
Updates: 1123

See examples near the end like SMTP at https://en.wikipedia.org/wiki/List_of_RFCs

@A2be
A2be commented Nov 1, 2016

If a 3rd party creates a token, and says "It's a standard, EC20 tokencontract.", does that then imply some standard JSON interface code would work to "watch" the contract with the Ethereum Wallet?

If so, where does one find that JSON interface?

(the question I'm asking is related to a comment on reddit: https://www.reddit.com/r/ethereum/comments/5aex87/arcade_city_caveat_emptor/d9go6sk/ )

@LefterisJP
Contributor

@A2be I don't see a json file but for the functions of the expected interface check the first post of this issue.

@frozeman
Member
frozeman commented Nov 2, 2016

I added the totalSupply public var. What else you guys would like to have added?

@Arachnid
Contributor
Arachnid commented Nov 2, 2016

Please don't use public variables; this means that in Solidity, at least, the contract can't calculate the value of this on the fly from other values; it's forced to maintain a precomputed value instead!

Also, shouldn't changes to a deployed standard require some more detailed review, as well as consideration for backwards compatibility with existing users?

@frozeman
Member
frozeman commented Nov 2, 2016

i thought it will generate the same getter function as when writing it manually. But if solidly deals with it differently then i will revert it. @chriseth can you give a comment on this?

@chriseth
Contributor
chriseth commented Nov 2, 2016

I think there are currently still some issues with inheritance and virtual functions related to accessor functions. I would advise to not use public variables in an interface because it will allocate a storage slot for that variable with is not the purpose of an interface.

@ethernomad

Which is the authoritative repo for token.sol?

@frozeman
Member
frozeman commented Nov 7, 2016

There is none, the issue above is currently. i try to keep it up to date.

We want to move to PRs in the future for EIPs and ERCs

@mikhail-vladimirov
mikhail-vladimirov commented Nov 29, 2016 edited

Attack vector on ERC20 API (approve/transferFrom methods) and suggested improvements: https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/ (commenting enabled)

@frozeman
Member

Thanks @mikhail-vladimirov. Its good to see security discussions added here. Lets discuss in the document.

@chriseth
Contributor
chriseth commented Nov 29, 2016 edited

Thanks, @mikhail-vladimirov! Let me write a quick summary: Re-setting an allowance from a nonzero value to another nonzero value allows a "double spend" with a front-running attack. A quick fix that does not require changing already deployed contracts is to not do that, but instead set the allowance to zero first, wait for confirmation and then set it to the other nonzero value.

@mikhail-vladimirov
mikhail-vladimirov commented Nov 29, 2016 edited

set the allowance to zero first, wait for confirmation and then set it to the other nonzero value

It is not enough to wait for confirmation. You need to check what exactly was the allowance right before it was set to zero. Otherwise double spend is still possible. You my not check this via Web3 API, so you need to use live.ether.camp of similar site that shows storage changes made by each transaction.

@chriseth
Contributor

@mikhail-vladimirov ah right, so one of the problems is that you do not see how much of the allowance was spent.

@chriseth
Contributor

This might be a good time to revisit some of the "cheques" proposals for tokens:

@koeppelmann
koeppelmann commented Dec 13, 2016 edited

@mikhail-vladimirov instead of "set_allowance" we could have a function:
increase_allowance(int number): old_allowance += number

@skrzypecki

Could someone help me to find a solution for the most effective way to calculate token dividends for current token standard?
I posted my question on ethereum.stackexchange:
http://ethereum.stackexchange.com/questions/10864/erc20-token-and-effective-rewa

@rolandkofler
Contributor

I'm interested in an overlapping topic: reputation points. The difference is that they are (1) not transferable, may have a (2) decay function over time as good deeds in the past are generally less valuable than current good deeds and (3) probably have infinite supply. It could be that you (4) burn them for actions like voting. It seems that balanceOf, allowance, name, symbol, decimals are canditates for this.

Is somebody interested to create a working group for an Reputation points ERC?

@miohtama

@rolandkofler Maybe a working group is little bit too heavy process for this. One can simple create a contract (proposal) and include in it contract packages like OpenZeppelin https://github.com/OpenZeppelin/zeppelin-solidity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment