ethereum / EIPs

The Ethereum Improvement Proposal repository

Home Page:https://eips.ethereum.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ERC: transferAndCall Token Standard

se3000 opened this issue · comments

Preamble

 ERC: 677
 Title: transferAndCall Token Standard
 Type: Informational
 Category: ERC
 Status: Draft
 Created: 2017-07-17
 Requires: ERC20

Simple Summary

Allow tokens to be transferred to contracts and have the contract trigger logic for how to respond to receiving the tokens within a single transaction.

Abstract

This adds a new function to ERC20 token contracts, transferAndCall which can be called to transfer tokens to a contract and then call the contract with the additional data provided. Once the token is transferred, the token contract calls the receiving contract's function onTokenTransfer(address,uint256,bytes) and triggers an event Transfer(address,address,uint,bytes), following the convention set in ERC223.

Motivation

ERC20 requires a multistep process for tokens to be transferred to a contract. First approve must be called on the token contract, enabling the contract to withdraw the tokens. Next, the contract needs to be informed that it has been approved to withdraw tokens. Finally, the contract has to actually withdraw the tokens, and run any code related to receiving tokens. This process typically takes two to three steps, which is inefficient and a poor user experience.

While ERC223 solves the described problem with its transfer(address,uint256,bytes) function, it opens other problems. ERC223 changes the behavior of ERC20's transfer(address,uint256), specifying that it should throw if transferring to a contract that does not implement onTokenTransfer. This is problematic because there are deployed contracts in use that assume they can safely call transfer(address,uint256) to move tokens to their recipient. If one of these deployed contracts were to transfer an ERC223 token to a contract(e.g. a multisig wallet) the tokens would effectively become stuck in the transferring contract.

This ERC aims to provide the helpful functionality of ERC223 without colliding with it. By giving contracts a reason to implement onTokenTransfer before ERC223 becomes widely implemented, a smooth transition is provided until a larger part of the Ethereum ecosystem is informed about and capable of handling ERC223 tokens. transferAndCall behaves similarly to transfer(address,uint256,bytes), but allows implementers to gain the functionality without the risk of inadvertently locking up tokens in non-ERC223 compatible contracts. It is distinct from ERC223's transfer(address,uint256,bytes) only in name, but this distinction allows for easy distinguishability between tokens that are ERC223 and tokens that are simply ERC20 + ERC667.

Specification

Token

transferAndCall

function transferAndCall(address receiver, uint amount, bytes data) returns (bool success)

Transfers tokens to receiver, via ERC20's transfer(address,uint256) function. It then logs an event Transfer(address,address,uint256,bytes). Once the transfer has succeeded and the event is logged, the token calls onTokenTransfer(address,uint256,bytes) on the receiver with the sender, the amount approved, and additional bytes data as parameters.

Receiving Contract

onTokenTransfer

function onTokenTransfer(address from, uint256 amount, bytes data) returns (bool success)

The function is added to contracts enabling them to react to receiving tokens within a single transaction. The from parameter is the account which just trasfered amount from the token contract. data is available to pass additional parameters, i.e. to indicate what the intention of the transfer is if a contract allows transfers for multiple reasons.

Backwards Compatibility

This proposal is backwards compatible for all ERC20 tokens and contracts. New tokens and contracts moving forward can implement the transferAndCall functionality, but also still fallback to the original approve-transferFrom workflow when dealing with legacy contracts. It does not require any changes or additional steps from already deployed contracts, but enables future contracts to gain this functionality.

Implementation

Example implementation.

What calls receiveApproval, and in what circumstances?

I see the point of structuring approveAndCall like this for backwards-compatibility reasons, but wouldn't a transferAndCall be a better option going forward? With this, you only need a single storage update, instead of two.

receiveApproval is called by the token contract, from within the approveAndCall function after the receiving contract has been approved to withdraw.

The reason for not using transferAndCall is that it would make it difficult, and often impossible, for the receiving contract to actually verify that tokens had been transferred to it(verifying would either require someone to check logs off chain, or a new API to be introduced). Since this function has to be public in order for tokens to call it, it would be very easy to maliciously call it and tell it that tokens were transferred without actually ever transferring tokens. By making the contract withdraw the tokens itself, the contract ensures that the transfer happens/succeeds.

Another reason which is less critical, but a nice feature, is that approveAndCall allows the contract some flexibility to withdraw tokens. If a price paid in tokens is dynamically calculated, you can list how much you're willing to pay, call the contract which then calculates how much is needed and withdraws only the required amount. Slightly more flexible.

I would rather see an addition to tokens that allows anyone to call transfer with an additional parameter that is a signature of the token holder. This way, dApps can simply ask the token holder (off-chain) for a signed allowance and then pass that to the transfer call when they need it. A bit more thought needs to be put into exactly how to protect against replay attacks, but I think this would result in a much simpler and more intuitive interface than the user having to interact with the dApp via the token (which is an awkward API).

The reason for not using transferAndCall is that it would make it difficult, and often impossible, for the receiving contract to actually verify that tokens had been transferred to it(verifying would either require someone to check logs off chain, or a new API to be introduced).

How so? User A calls transferAndCall of contract C, contract C then calls receiveTransfer of contract B. Contract B sees that msg.sender = C, and so it's a legit transfer of tokens.

The reason why transferAndCall is superior is as @Arachnid says, it allows you to get rid of any expensive superfluous storage updates whatsoever - all that happens is tokens get transferred and the recipient gets notified.

If a price paid in tokens is dynamically calculated, you can list how much you're willing to pay, call the contract which then calculates how much is needed and withdraws only the required amount. Slightly more flexible.

This is also doable with transferAndCall: you would first call some constant function getHowMuchINeedToPay of the recipient, then send a transferAndCall with that precise amount.

The worst case I was imagining is something like a decentralized exchange dealing with lots of tokens they can't evaluate, but just enable people to use them. In this case people could introduce malicious tokens, but as I think more about it, a malicious token contract could also sabotage the approveAndCall functionality.

The getHowMuchINeedToPay pattern would work, but the dynamic gas pattern wouldn't be available with as a standardized behavior.

I'm open to transferAndCall, but leaning towards including both. Is the address token parameter necessary? It seems like it wouldn't be for transferAndCall but maybe still allows some flexibility with approveAndCall. What is enabled by taking the parameter and comparing to msg.sender, as opposed to just using msg.sender as the token parameter? The main benefit I see is fitting the existing pattern that some tokens have used.

Can someone explain me what is the reason of having approves in standard tokens?
They are needed for nothing since the common pattern is just to handle an incoming transaction.
ETH transfers works as above and are handled by payable functions (fallback in most cases).

As I've supposed earlier that the only purpose of approval mechanism was to prevent stack depth attack.
#223 (comment)

So the only reason of approves is backwards compatibility with ERC20?

@Dexaran backwards compatibility seems like the most important reason to me. Beyond that, approved withdrawal caps are a pretty standard pattern that have unique workflows that would be more difficult without the approve method.

@MicahZoltu Interesting proposal, it looks as if it would fit the pattern proposed by EIP662. It seems like a broad pattern which may be orthogonal and complimentary to token transfer standards.

approved withdrawal caps are a pretty standard pattern that have unique workflows that would be more difficult without the approve method.

@se3000
What unique workflows are you talking about? I ask for an example over the past three months, but I have never received any real answer. Only answers such as "a useful functional that you must believe exists, but no one has seen it."

By the way Ether dosn't have any approves and everything is OK with it.

@Dexaran I'm with you on wanting to remove approvals entirely. However, the workflow that is meaningful to me is the one where you have some complicated system (such as Augur) that has need to move user tokens around internally. Having the user start the interaction with a token transfer works, but it complicates the interface because it means the entrypoint for Augur is always through a token, rather than through a contract call. Since the details of the interaction are coming through via the data parameter, this means that the receiving contract needs to decode those bytes (in-contract) and then make decisions based on the data. Not only are these bytes opaque to any user looking at the transaction (which includes tools designed to show transaction details using the ABI), decoding them into something useful on-chain is hard.

For the most naive case, the data could just be a number (effectively part of an enum), and this isn't too bad. However, this won't work for all cases such as an exchange where you need to send quite a few details along with the token transfer like counterparty address, token being traded for, price, etc. All of this needs to be encoded into a byte array that is totally opaque to any reader.

Note: The above aren't insurmountable problems, and I'm not sure approve is really any better of a solution because it requires multiple signatures from the user (something a UI can't hide), but I do think it is something worth considering.

Approvals have one other problem: using approvals creates a workflow where the contract that uses the approval needs to do something like: (i) call transferFrom, THEN (ii) do something else. This is an external call followed by a state change, which is generally considered a no-no because of re-entrancy issues.

I now increasingly support transferAndCall as defined in #223, as the execution path is very clean - first go into the token contract, edit balances there, then go into the destination account.

I previously had the objection that it complicates implementation since you would need to be able to do "ABI inside ABI", but I have since softened on this since realizing that multisig contracts and other forwarding contracts require this already.

Also, stack depth attacks are irrelevant since the Tangerine Whistle hard fork (that's last October).

Ok, thanks for the feedback so far. Based on the helpful feedback, I've switched this to transferAndCall and updated the spec at the top.

At first I thought that ERC223 was sufficient to offer the transfer and call functionality. Based on further consideration it seems like not every token will want to be ERC223 compatible, as some contracts using ERC20 today do not allow for an approve - transferFrom withdrawal process. Such contracts would end up unintentionally holding ERC223 tokens and not allowing them to be withdrawn by contracts, because transfer(address,uint256) throws an error when sending to a contract; a common use case being a multisig wallet receiving tokens from another contract.

transferAndCall(address,uint256,bytes) as currently proposed is effectively the same as ERC223's transfer(address,uint256,bytes), calling the same tokenFallback(address,uint256,bytes) function on the receiver, and triggering the same Transfer(address,address,uint256,bytes) event. This allows contracts set up to receive tokens via ERC667 to be compatible with ERC223 transfers in the future and vice versa. By providing a step that allows for ERC20 tokens to call tokenFallback, it provides a transition path, until more contracts are prepared to handle receiving ERC223 tokens.

Feedback is basically the same as I provided to ERC223. onTokenTransfer rather than tokenFallback and I'm still a fan of transferAndCall taking in a string name of the function to call (with onTokenTransfer being default). I also recommend using the method name transfer rather than transferAndCall since Solidity supports function overloads and IMO having several functions named transfer with different parameters is more pithy than having several functions all with different names and different parameters.

Why specify a fixed function at all? It seems to me it would make a lot more sense to pass in a bytes argument and call the target contract with that as a raw payload.

@Arachnid Similar argument to that over in the URI EIP. Human readability, which leads to increased security by way of allowing users to make an informed decision. When prompted to sign, if I see transfer(0xdeadbeef, 100, 0xabcdef0123456789) all I know is that I'm transferring 100 tokens to the 0xdeadbeef contract but I don't know anything about how those tokens will be used. For something like Augur (a large project), there are many ways in which a token transfer can be used. On the other hand, if I see: transfer(0xdeadbeef, 100, "buyShare", 0xabcdef0123456789) I at least know that the tokens will be used to buy a share. I haven't figured out an easy way to have the function parameters decoded (which would be even better) but this is at least a step in the right direction. Ideally the call would look something like transfer(0xdeadbeef, 100, "buyShare", "LONG", 200) to indicate that I want to buy 200 long shares for the price of 100 tokens.

Similar argument to that over in the URI EIP. Human readability, which leads to increased security by way of allowing users to make an informed decision.

This is a standard for an ABI, though - human readability shouldn't come into it, and certainly not as an overriding consideration. Further, with onTokenTransfer being the only fallback function, you won't get any useful information for the user anyway.

Insofar as you can decode the outer call's ABI encoding to show something to the user, too, you could also decode the inner encoding in the same fashion.

Why specify a fixed function at all? It seems to me it would make a lot more sense to pass in a bytes argument and call the target contract with that as a raw payload.

The receiving contract should know who sent the tokens and the amount sent. If that is specified in the bytes then the transferrer could lie and say they sent more than they did. Making the token contract report ensures the correct parameters are passed. Or, is there a way I'm missing to make sure the bytes accurately represent the transfer?

The receiving contract should know who sent the tokens and the amount sent. If that is specified in the bytes then the transferrer could lie and say they sent more than they did. Making the token contract report ensures the correct parameters are passed. Or, is there a way I'm missing to make sure the bytes accurately represent the transfer?

Information about the transfer does not need to be contained in the message payload; the contract can identify the caller and check its balance there.

Information about the transfer does not need to be contained in the message payload; the contract can identify the caller and check its balance there.

Current balance is very different from amount received. In order for current balance to work/be useful, the contract would need to track "last known balance" for every token it receives and update it on every token receipt which means at least 5k additional gas for the state update. This also assumes that the contract isn't receiving tokens via some other means (e.g., traditional ERC20 token transfers).

Insofar as you can decode the outer call's ABI encoding to show something to the user, too, you could also decode the inner encoding in the same fashion.

This would require tooling to directly implement support for tokens implementing this. Using the scheme I described above, the tooling only needs to be able to decode/display top level contract calls (which some tools like Parity already do).

@Arachnid Is your assumption that all tools will directly support this spec and understand what the bytes parameter represents, and decode it to display to users in the same way they decode top-level method parameters? I'm not sure where the appropriate place to have this discussion is, but you and I seem to be approaching several EIPs from different angles. I believe we both recognize the value in signing tools being able to provide users with enough details so the user can make an informed signing decision, but in each of the EIPs you seem to be favoring low-level bytearrays while I am favoring higher level APIs. I'm curious what your vision is for the ecosystem that allows for low-level byte array APIs while still providing users the information at signing time to make informed signing decisions.

At a high level, byte arrays are a lossy interface (method name and types cannot be recovered from method hash) which is what I'm arguing against. I think the data that is lost is very important for informed signing decisions. How do you propose allowing users to re-acquire this lost information so they can make an informed signing decision?

The current balance approach is interesting. As Micah pointed out it's expensive and not guaranteed to work if tokens are received in other ways. That is probably solvable, but would increase the complexity of writing any contract that receives tokens.

Similarly, how would you determine who sent the tokens? Assuming not tx.origin, that would also have to be specified in the bytes data and could then be faked. Attributing a transfer to someone else doesn't sound malicious, but it could be problematic. Also, attributing a transfer to someone else is still possible for the receiving contract to implement with transferAndCall.

Stepping back a bit, I'm currently of the mind that token transfers would ideally behave as much like Ether transfers as possible. For that to remain, you should always be able to tell who sent you a token, like an equivalent to msg.sender.

@se3000 do you have an example implementation yet? I assume the receiving contract will be the same as this implementation of the ERC223 token: https://github.com/Dexaran/ERC223-token-standard/blob/Recommended/Receiver_Interface.sol

As a token contract author with an upcoming token sale in the coming weeks, I'm torn on whether to stick to the ERC20 standard, use this standard with transferAndCall, or use the ERC223 standard.

@iam-peekay I recommend going with ERC20 for now. You can always release a new token and have users migrate once ERC223 or this is finalized.

@MicahZoltu @se3000 Good points; being able to determine the ultimate sender and the amount they sent are critical pieces of information that have to be supplied in a trusted fashion. I withdraw my suggestion.

I'm not sure where the appropriate place to have this discussion is, but you and I seem to be approaching several EIPs from different angles. I believe we both recognize the value in signing tools being able to provide users with enough details so the user can make an informed signing decision, but in each of the EIPs you seem to be favoring low-level bytearrays while I am favoring higher level APIs. I'm curious what your vision is for the ecosystem that allows for low-level byte array APIs while still providing users the information at signing time to make informed signing decisions.

Ultimately, I'm a fan of representing things in the form most suited to the layer in which they reside, and of minimising unnecessary overhead inside the EVM. That means user-friendly standards for URIs, but machine-level specifications for ABIs.

User insight can be provided by using a higher level encoding to determine what to display to the user, then deriving the ABI level encoding from that, rather than trying to reverse-engineer ABI encoding or embed extra metadata in it.

Showing function calls to a user is a horrible user experience anyway, and for anyone other than a programmer, only marginally more meaningful than just showing them the raw ABI encoded hex data.

Edit: Another alternative to consider is approveAndCall; this has the significant advantage that it Just Works with existing contracts that consume tokens, and doesn't require passing any trusted data to the callee.

@iam-peekay @MicahZoltu

Advising people "i recommend going with ERC20 for now" is exactly the same as saying "your users will guaranteed lose money because of vulnerability of this standard but I will still recommend to use it. It's just someone else's money, you don't need to care about it."
Here is a short demonstration about how people will suffer loss of funds if you will go with ERC20:
I will recommend to carefully read this comment:
#223 (comment)

You guys cast a bad shadow on the whole cryptocurrency industry, developing tokens so that people lose money and advising other developers to do the same just because you don't think that it is serious that someone will suffer because of your inattentiveness.

https://medium.com/@dexaran820/erc20-token-standard-critical-problems-3c10fd48657b

https://www.reddit.com/r/ethereum/comments/6h17og/critical_problem_of_erc20_tokens_effect_appeal_to/?st=j7hhqkxe&sh=cfc5ae37

Advising people "i recommend going with ERC20 for now" is exactly the same as saying "your users will guaranteed lose money because of vulnerability of this standard but I will still recommend to use it. It's just someone else's money, you don't need to care about it."

No, it's not. ERC223 is in a state of flux, and it's far from clear that it won't lead to its own causes of lost or locked funds. Recommending people implement a standard that isn't even close to being locked down is a recipe for chaos.

If you implement ERC20 then your users will lose money.
If you implement ERC223 then it depends on how you will implement it.

I prefer not to do what is guaranteed to be a mistake and result in loss of money for users.

Implementing ERC223 right now is equivalent of not implementing any standard. You are welcome to create a non-standard token, it has been done before. If you want your token to be interoperable though, you have to pick a standard and right now there is only one (ERC20).

If you implement ERC20 then your users will lose money.

If you implement ERC223 then it depends on how you will implement it.

I prefer not to do what is guaranteed to be a mistake and result in loss of money for users.

To phrase it another way: Would you prefer a small but well-known risk of loss, or a completely unknown risk of loss that could be much smaller or much larger?

The precautionary principle errs in favor of the former.

@iam-peekay I'll post an example soon. The receiving contract's interface will be the same as the example you posted.

I think ERC223 is too risky at the moment, with the possible accidental lock-in mentioned in the Motivation section of the spec. In the meantime, you can prevent many accidental token burns by throwing for transfers to 0x0 or the token itself, I believe those are the two most common burns.

You could implement something like ERC677 now and get the "1 transaction transfer to contract" functionality. The standard may evolve, but you will at least have the functionality available to your token.

@MicahZoltu to your point about naming, this method is intentionally different than ERC223's method names. I believe avoiding overlap will make it easier to distinguish a token's behavior.

Having a distinct name also comes with the added benefit of making it easier on client libraries to distinguish which method you mean to call. Having written some tests for ERC223 with truffle, I ended up having to manually build the data parameters for transactions because the web3 contract objects would get confused about which version of transfer I intended to call, despite the method signature clearly being distinct.

@Arachnid Would you prefer a small but well-known risk of loss, or a completely unknown risk of loss that could be much smaller or much larger?

Not a risk. It is a well-known fact that it will cause losses. Not so small, $500,000 is not a small risk. The amount will always grow with time. If you use ERC20 then you will open a black hole where the money will be lost in a future.
Why are you saying about completely unknown risks when using a new token standard? I thought that you are competent enough to investigate and make a decision whether a standard is risky or not. Saying that the new implementation is worse because it contains an unknown mystic risks makes me think that you don't want to investigate but to stay with the current implementation even if it will hurt your users.
I hope that I'm wrong and you have a very serious reasons to advise Ethereum developers to make their users to lose millions of dollars.

Not a risk. It is a well-known fact that it will cause losses.

Poor UI combined with an issue with ERC20 can cause a loss - and better UI can mitigate it. For an individual user, it's a risk of loss.

Not so small, $500,000 is not a small risk. The amount will always grow with time. If you use ERC20 then you will open a black hole where the money will be lost in a future.

Absolute amounts are meaningless; what's relevant is the proportion of funds that are put at risk.

Why are you saying about completely unknown risks when using a new token standard?

Because every new standard has risks. If that weren't the case, this issue with ERC20 would not have occurred in the first place.

Saying that the new implementation is worse because it contains an unknown mystic risks makes me think that you don't want to investigate but to stay with the current implementation even if it will hurt your users.

I hope that I'm wrong and you have a very serious reasons to advise Ethereum developers to make their users to lose millions of dollars.

Everyone is working towards the same goal here. Accusing people of malfeasance and carelessness because you disagree with their technical assessments or priorities does nothing to advance the conversation. Please stop.

@iam-peekay based on the discussion around ERC223, it's looking like that will no longer be the interface. It looks like tokenFallback will be replaced by onTokenTransfer. There's an implementation with tests here. We're planning to deploy it soon. I'll post a more generic version in the future.

@se3000 cool thanks. I am following @MicahZoltu and @Arachnid's advice and sticking to ERC220 for now until the interface is finalized. Feels like still a lot of moving parts on that one.

I'm working in an experiment Token which I call TxToken.

It's like a MiniMeToken but it store the transactions, so if 0x01 sends token to 0x02, this would be saved, and a user could go in the other contract an signal the watch of that token.

This transaction list storage is useful for some features in my projects (like TokenLedger contract), when an ERC20 is sent directly the contact is unable to know from whom come the token, therefore there is no trustless refund.

@MicahZoltu

I also recommend using the method name transfer rather than transferAndCall since Solidity supports function overloads and IMO having several functions named transfer with different parameters is more pithy than having several functions all with different names and different parameters.

I am with Micah here. By making this a secondary transfer call with extra parameters we could in theory move to a point in which this is just the new default transfer calls always, and we wouldn't need the extra gas cost of #223.

Feedback is basically the same as I provided to ERC223. onTokenTransfer rather than tokenFallback and I'm still a fan of transferAndCall taking in a string name of the function to call (with onTokenTransfer being default).

I am indifferent to the fallback function name, at the moment I see receiveApproval being used on the home page, onTokenTransfer on 223 and tokenFallback here. I will support wichever is more popular

Until now, msg.sender has implied agency: an account has permission to take an action because it's the owner. With this pattern, msg.sender implies provenance: the event the account is reporting definitely happened because it controls that event. It will be important to make sure that people don't use both interpretations within the same contract. A token contract that can own arbitrary assets can probably also report arbitrary events. Maybe no one was ever going to write a token contract with a wallet baked in, but now they definitely should not. If we standardize the callback function names for this pattern beyond tokens (e.g. onX or xFallback), it'd at least be possible to trigger warnings when people mix them with wallet-style functions in the same contract.

Notice that tokenFallback from ERC223 does not returns bool.

So what happens if "tokenFallback" returns false? The tokens have been transferred and you cannot get them back.

After an excessive research for an up-gradable mechanism for a token I think that (right now) the transferAndCall + approveAndCall is the best one, and I want to implement it on our token.
Is there any reference implementation out there or I will just create my own and share it with you after?

When it return false the transfer of tokens should fail. This prevents this kind of problem: dapperlabs/cryptokitties-bounty#3

Let's revive the discussion here.

I think there are two main things that should be addressed:

  • Return value and its interpretation. ERC223 got rid of it, so maybe we should follow that. On the other hand, it's also quite easy to implement @chfast - just revert() when the fallback returns false. And it would prevent the problem mentioned by @3esmit, so there is some advantage to having it.

  • Overloaded event. Looking at e.g. the LinkToken implementation, they extend an ERC20 token, do super.transfer and then trigger the event. I'm not sure this is a clever way to do it, as there will be two Transfer events. Either transferAndCall should trigger a single Transfer event or there should be an additional ERC677Transfer event. The proposal would benefit from more clarity here.

E.g. this is what happens in truffle develop for an implementation like mentioned above:

logs0: { logIndex: 0,
  transactionIndex: 0,
  transactionHash: '0x2803365769002dfdd0012925d3bfc8612ac56cba76b0028cc131936d3e4e51e2',
  blockHash: '0x0a7738fd01d31f153413958f15629144c14181c80531ea52ccdfadf5325fe1f3',
  blockNumber: 12,
  address: '0xf204a4ef082f5c04bb89f7d5e6568b796096735a',
  type: 'mined',
  event: 'Transfer',
  args: 
   { _from: '0x627306090abab3a6e1400e9345bc60c78a8bef57',
     _to: '0xf17f52151ebef6c7334fad080c5704d77216b732',
     _value: { [String: '123'] s: 1, e: 2, c: [Object] } } }
logs1: { logIndex: 1,
  transactionIndex: 0,
  transactionHash: '0x2803365769002dfdd0012925d3bfc8612ac56cba76b0028cc131936d3e4e51e2',
  blockHash: '0x0a7738fd01d31f153413958f15629144c14181c80531ea52ccdfadf5325fe1f3',
  blockNumber: 12,
  address: '0xf204a4ef082f5c04bb89f7d5e6568b796096735a',
  type: 'mined',
  event: 'Transfer',
  args: 
   { _from: '0x627306090abab3a6e1400e9345bc60c78a8bef57',
     _to: '0xf17f52151ebef6c7334fad080c5704d77216b732',
     _amount: { [String: '123'] s: 1, e: 2, c: [Object] },
     _data: '0x' } }

Also regarding the name (tokenFallback vs onTokenTransfer), while I saw 1-2 mentions of the latter I think there's far from a conses for it in #223. Did I miss something?

Also how should transferAndCall handle transfers to regular accounts? For ERC223 it makes sense to simply not do the call - however when using transferAndCall the intention could be to actually trigger a call and sending to an account would indicate a problem.

I implemented both erc223 and erc677:
https://github.com/colucom/CLN-solidity/tree/erc223
https://github.com/colucom/CLN-solidity/tree/erc677

This is the comparison between them both:
ColuLocalNetwork/CLN-solidity@erc223...erc677

I would love to get comments on my implementations since I didn't find any production ready implementation on any other token for this EIP.

  • I commented the same on the EIP223 page

Critical security vulnerability: https://github.com/colucom/CLN-solidity/blob/erc677/contracts/Standard677Token.sol#L15

You can not rely on return values in solidity. ethereum/solidity#2630

Example:

import './Standard677Token .sol';
contract A is Standard677Token {
//your ERC677 token.
}

contract B {
// fake receiver

    function() payable
    {
        // no return values here!
        // this should not handle tokens as well.
    }
}

If you will call A.transferAndCall(B, 10, 0x0); then contract B will successfully return a random value from stack and likely it will be true and everything will happen as if B has successfully handled the transaction, but it has not.

@Dexaran So?
Don't transferAndCall to a contract that "fake" the receiving protocol.
The require (in line 22) doesn't safe guard you from transferring tokens to a non "truly" erc223 receivers, but if they do, then the transfer will fail when the inner method didn't returned true.

To my understanding, the main problem here is that ERC223 reverts if the recipient doesn't include the tokenFallback() function. Although this was intentionally done by Dexaran, to prevent the loss of funds.

Why not, instead of implementing a new transferAndCall() function, just make a low-level call on the transfer?

_to.call(bytes4(keccak256("tokenFallback(address,uint256,bytes)")),msg.sender, _value, _data);

This won't fail if the recipient does not include the tokenFallback, so it can be sent to multisig wallets. It does return a bool which tells if the call was successful or not, but as long as you don't use the return value the transfer will complete either way.

Example of ERC677 transfer() function:

    function transfer(address _to, uint _value, bytes _data) {
        uint codeLength;

        Transfer(msg.sender, _to, _value, _data);

        assembly {
            codeLength := extcodesize(_to)
        }

        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        if(codeLength > 0) {
            _to.call(bytes4(keccak256("tokenFallback(address,uint256,bytes)")),msg.sender, _value, _data);
        }
    }

However, as previously mentioned, the transaction in ERC223 is meant to fail. Otherwise funds are lost.

It seems there's some info missing in the tokenFallback function? With a contract designed to receive incoming token transfers and implementing the tokenFallback function, it will get notified when some token is transferred to it, but how will it know WHICH token it receives?

For example a token wallet contract that can hold multiple tokens, when the tokenFallback function is called, how does it know which token it is receiving? shouldn't tokenFallback function have one more address parameter to tell it which token it has received?

@wuya666 the receiver can use msg.sender to look up the token address.

Note that for the receiver, msg.sender is not the user who submits the tx but the contract who called the tokenFallback. This is why we pass the msg.sender address with the tokenFallback function, so that the receiver can know who (i.e. user) made the tx.

User calls transfer on the token contract.
Token contract calls tokenFallback on the receiver contract.

Hey @se3000 ,

In transferFrom() in linkStandardToken.sol can we make a gas optimization by doing all the checks first? i.e. move line (6) to be run before lines (4) or (5).

function transferFrom(address _from, address _to, uint256 _value) returns (bool) {
(1)    var _allowance = allowed[_from][msg.sender];

(2)    // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met
(3)    // require (_value <= _allowance);

(4)    balances[_from] = balances[_from].sub(_value);
(5)    balances[_to] = balances[_to].add(_value);
**(6)    allowed[_from][msg.sender] = _allowance.sub(_value);**
(7)    Transfer(_from, _to, _value);
(8)    return true;
}

Given the current sequence, we are checking for allowance (line 6) only after adding the balance (line 5). If it fails then, gas is wasted in (4) and (5).

@nczhu yes, you could.

The linkStandardToken is a renamed version of an old copy of the OpenZeppelin StandardToken. We renamed it to avoid name collisions for developers importing Chainlinked.sol into Solidity suites that used other versions of StandardToken.

In fact, the current version of the OpenZeppelin ERC20 token has the optimization you're suggesting.

commented

I know this EIP cause I am facing such case: in the most cases, if I want to transfer some tokens to a contract, I will use approve() and transferFrom(), but this will generate two transactions, so this is a bad experience for users in a dapp, so I want to know whether there is an approach which can do these in one transaction, so I find this EIP, but it seems like something different, because in my case, although the user will have got to generate two transactions, they will not approve later, because at first, the dapp will ask users to approve -1 for the contract, so later, the contract just call token.transferFrom() is enough, but for ERC677, it seems not, it really makes these into one transaction, but this will generate every time when the user want to transfer tokens to the contract, and this will cost more gas. So it seems like this mode is not suitable for my case.

@Skyge Have you checked out ERC-777? It is currently in Last Call I believe (or maybe Final) and includes a mechanism for doing transfer and call. I suspect it will be the next standard token after ERC-20.

commented

OK, I will check it later. Thanks!

I do not understand the need for a token standard here the logic described should be better developed in a standard smart contract? Or am I missing something here?

@Skyge Have you checked out ERC-777? It is currently in Last Call I believe (or maybe Final) and includes a mechanism for doing transfer and call. I suspect it will be the next standard token after ERC-20.

I doubt that. ERC-777 seems overly complex to me. Even ERC-677 should be reduced to only include transferAndCall. approveAndCall is not necessary as the purpose of transferAndCall is exactly to obsolete the approval mechanism in most cases. Also, I think it is save to assume the that caller knows what he is doing, so there is no need for an isContract check. In the end, this boils down to only adding this small snippet to every ERC-20 token:

function transferAndCall(address recipient, uint amount, bytes calldata data) public returns (bool) {
    bool success = transfer(recipient, amount);
    if (success){
        IERC677Receiver(recipient).onTokenTransfer(msg.sender, amount, data);
    }
    return success;
}

Furthermore, I wouldn't return anything for "onTokenTransfer". The proper behavior if you want a function call to fail is to just let if fail with a require statement or so. No need to return a success flag. For ERC-20, this is too late to change. But we do not need to repeat that mistake here.

Here's the receiver interface without return value:

interface IERC677Receiver {

function onTokenTransfer(address from, uint256 amount, bytes calldata data) external;

}

@se3000 Is this EIP still of interest to you, or should we treat it as abandoned?

I am wondering how close this is to the new ERC777?

I am wondering how close this is to the new ERC777?

What I wrote above is quite close to the tokensReceived function of ERC777. The problem I have with ERC-777 is that it is bloated (I prefer a clean minimalist solution) and not backwards compatible with ERC-20.

To quote their specs: "It is backward compatible with wallets that do not contain the tokensReceived hook function by deploying a proxy contract implementing the tokensReceived hook for the wallet." This, of course, is a convoluted way of saying that it is not backwards compatible. Contracts designed to handle ERC-20 cannot handle ERC-777 and that the tokens instead should be sent to another ("proxy") contract that holds them on behalf of the other contract. I find calling ERC-777 "fully compatible" is quite bold.

@MicahZoltu The EIP has slipped my mind recently but is not abandoned. LINK uses it and it is great for minimizing transactions and/or inter-contract interactions, along with the potential issues those raise(e.g. re-entrancy). Until another token standard with similar functionality emerges as dominant, I think 677 compliments ERC-20 well, and I'd advocate for more widespread adoption.

What are the next steps to move this forward in the EIP process?

@se3000 The first step is to create an actual EIP. Right now this is just an idea (GitHub issue for discussing with others whether something is worthy of being an EIP). Anyone can create an EIP, just submit a pull request and make sure the EIP matches the template (https://raw.githubusercontent.com/ethereum/EIPs/master/eip-template.md).

After that you can iterate on it as necessary, and when you think it is ready you can change the status from Draft to Last Call with a 2 week review period. Assuming the editors don't have a problem with the structure/formatting/wording, it'll go into Last Call for at least two weeks before finally merging as Final (assuming no problems crop up during last call).

@se3000 is it possible to update "Example implementation." link?

se3000

@PawelRokosz @curtcurt871 Updated. There is also a Hardhat/Waffle shared behavior test here.

What I wrote above is quite close to the tokensReceived function of ERC777. The problem I have with ERC-777 is that it is bloated (I prefer a clean minimalist solution) and not backwards compatible with ERC-20.

This is exactly my thoughts after reading the ERC777 spec. Somehow they seem to solve the problem of forgoing an additional Approval transaction but it seems they've over-engineered when all that's necessary is an additional call to e.g. update balances.

One issue I didn't see mentioned is that if there is JS code that handles "generic ERC20" then it might be incompatible with ERC667. Overloading the Transfer name means scripts that wait for "Transfer" event break with error "Duplicate definition of Transfer (Transfer(address,address,uint256,bytes), Transfer(address,address,uint256))" or similar. Why was the event not named something like TransferAndCall or TransferWithData or something like that?

Again, @MicahZoltu can we consider this superseded by https://eips.ethereum.org/EIPS/eip-4524? It has nearly the exact same functionality.

Again, @MicahZoltu can we consider this superseded by https://eips.ethereum.org/EIPS/eip-4524? It has nearly the exact same functionality.

Currently, this is just an idea, not an EIP, so it cannot be superceded by anything. Separately, superseding is not something editors do because that would mean we are taking an opinionated position on which EIPs should supersede which shouldn't. If this was an actual final EIP, the authors of 4524 could mention that 4524 is meant to supersede this one but that wouldn't be any sort of official thing, just the opinion of the authors of 4524.


Regarding this issue though, I am going to close it as this is no longer the right place to pursue this idea further. The correct next course of action @se3000 is to either continue discussion on Ethereum Magicians, or create a draft EIP for this idea.

I'd like to point out that since this is already in use in many high value token contracts, it's misleading to call it a mere "idea". EIPs, even accepted ones, are full of "ideas" that never (yet) have seen practical implementation. It ought to be considered a shortcoming in the EIP process itself if it cannot stay ahead of or even closely track the state of the art; if it aspires to be normative but fails to even be exhaustively descriptive. This is not an individual failure, of course, but social/organizational one; also it's not a dramatic failure, since it's very easy to fix once there's agreement on all sides to do the standardization work.

Demoting the actual state of the art to the level of "just an idea" probably doesn't help in the way of forming an agreement.

@jtakalai The truth is, ERC-1363 went through the EIP process, got finalized, and I would argue is a better design than issue 677.
So yes, many projects implementing this interface instead of 1363 is definitely not an individual failure. If there is anyone to blame, I would argue the blame does NOT go toward the EIP process, but toward all the devs that ignored this process and implemented 667 regardless.

And yes, the fact that 667 is just written down in an issue, without link to a proper discussion, and without a PR to make it actually into the repo means that 667 is definitely NOT an ERC. Calling it an idea is possibly the most accurate thing you could say about it.

I'd like to point out that since this is already in use in many high value token contracts, it's misleading to call it a mere "idea"

By definition, this is not an official Ethereum standard as there is no Final EIP for it. It may be a defacto standard (there are many such things in the wild), but it isn't an EIP, it doesn't have a number, it doesn't exist as an official Ethereum standard, etc.

If you want to turn this into an actual numbered standard you are welcome to, just create a pull request against this repository adding it following the instructions in EIP-1.

The important part is that this issue stays online so anyone googling ERC 677 gets a rough idea about its purpose. I'm one of the developers usually adding ERC 677 support to tokens as it is a relatively light-weight and convenient "standard" in comparison to the more heavy-weight proposals.

For the future, what I would wish for was a convenient and light-weight standards for "permit" that can be signed offline. And by light-weight I mean low in gas fees. This could also cover some of the "transfer and call" use cases if the receiving contract supports the permits.

The important part is that this issue stays online so anyone googling ERC 677 gets a rough idea about its purpose. I'm one of the developers usually adding ERC 677 support to tokens as it is a relatively light-weight and convenient "standard" in comparison to the more heavy-weight proposals.

I would like to point in the direction of https://eips.ethereum.org/EIPS/eip-1363, a final EIP that is effectively this proposal.

The important part is that this issue stays online so anyone googling ERC 677 gets a rough idea about its purpose.
This issue will remain, but it is important that everyone understand that there is no ERC-677. There may be an ERC-677 in the future, but it may have nothing to do with the concepts put forward in this issue. EIP editors assign EIP numbers during the first step of the EIP process, after a pull request is submitted. At some point in the future we may assign 677 to someone else.