New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ERC: transferAndCall Token Standard #677

Open
se3000 opened this Issue Jul 19, 2017 · 52 comments

Comments

Projects
None yet
@se3000
Copy link

se3000 commented Jul 19, 2017

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.

@Arachnid

This comment has been minimized.

Copy link
Collaborator

Arachnid commented Jul 20, 2017

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.

@se3000

This comment has been minimized.

Copy link

se3000 commented Jul 21, 2017

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.

@MicahZoltu

This comment has been minimized.

Copy link
Contributor

MicahZoltu commented Jul 21, 2017

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

@vbuterin

This comment has been minimized.

Copy link
Collaborator

vbuterin commented Jul 25, 2017

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.

@se3000

This comment has been minimized.

Copy link

se3000 commented Jul 25, 2017

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.

@Dexaran

This comment has been minimized.

Copy link

Dexaran commented Jul 29, 2017

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?

@se3000

This comment has been minimized.

Copy link

se3000 commented Aug 1, 2017

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

@se3000

This comment has been minimized.

Copy link

se3000 commented Aug 1, 2017

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

@se3000 se3000 changed the title ERC: approveAndCall Token Standard ERC: approveAndCall/transferAndCall Token Standard Aug 1, 2017

@Dexaran

This comment has been minimized.

Copy link

Dexaran commented Aug 2, 2017

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.

@MicahZoltu

This comment has been minimized.

Copy link
Contributor

MicahZoltu commented Aug 2, 2017

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

@vbuterin

This comment has been minimized.

Copy link
Collaborator

vbuterin commented Aug 3, 2017

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

@se3000 se3000 changed the title ERC: approveAndCall/transferAndCall Token Standard ERC: transferAndCall Token Standard Sep 11, 2017

@se3000

This comment has been minimized.

Copy link

se3000 commented Sep 11, 2017

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.

@MicahZoltu

This comment has been minimized.

Copy link
Contributor

MicahZoltu commented Sep 11, 2017

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.

@Arachnid

This comment has been minimized.

Copy link
Collaborator

Arachnid commented Sep 11, 2017

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.

@MicahZoltu

This comment has been minimized.

Copy link
Contributor

MicahZoltu commented Sep 11, 2017

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

@Arachnid

This comment has been minimized.

Copy link
Collaborator

Arachnid commented Sep 11, 2017

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.

@se3000

This comment has been minimized.

Copy link

se3000 commented Sep 11, 2017

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?

@Arachnid

This comment has been minimized.

Copy link
Collaborator

Arachnid commented Sep 11, 2017

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.

@MicahZoltu

This comment has been minimized.

Copy link
Contributor

MicahZoltu commented Sep 11, 2017

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

@MicahZoltu

This comment has been minimized.

Copy link
Contributor

MicahZoltu commented Sep 11, 2017

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?

@se3000

This comment has been minimized.

Copy link

se3000 commented Sep 11, 2017

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.

@iam-peekay

This comment has been minimized.

Copy link

iam-peekay commented Sep 12, 2017

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

@MicahZoltu

This comment has been minimized.

Copy link
Contributor

MicahZoltu commented Sep 12, 2017

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

@Arachnid

This comment has been minimized.

Copy link
Collaborator

Arachnid commented Sep 12, 2017

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

@sgitt-vassky

This comment has been minimized.

Copy link

sgitt-vassky commented Sep 12, 2017

@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

@Arachnid

This comment has been minimized.

Copy link
Collaborator

Arachnid commented Sep 12, 2017

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.

@sgitt-vassky

This comment has been minimized.

Copy link

sgitt-vassky commented Sep 12, 2017

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.

@MicahZoltu

This comment has been minimized.

Copy link
Contributor

MicahZoltu commented Sep 12, 2017

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

@alexvandesande

This comment has been minimized.

Copy link
Contributor

alexvandesande commented Sep 25, 2017

@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

@niran

This comment has been minimized.

Copy link

niran commented Sep 25, 2017

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.

@3esmit

This comment has been minimized.

Copy link

3esmit commented Oct 24, 2017

Notice that tokenFallback from ERC223 does not returns bool.

@chfast

This comment has been minimized.

Copy link
Contributor

chfast commented Oct 26, 2017

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

@tal-beja

This comment has been minimized.

Copy link

tal-beja commented Nov 22, 2017

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?

@3esmit

This comment has been minimized.

Copy link

3esmit commented Dec 3, 2017

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

@svenstucki

This comment has been minimized.

Copy link

svenstucki commented Dec 20, 2017

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?

@svenstucki

This comment has been minimized.

Copy link

svenstucki commented Dec 20, 2017

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.

@tal-beja

This comment has been minimized.

Copy link

tal-beja commented Dec 25, 2017

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

This comment has been minimized.

Copy link

Dexaran commented Dec 25, 2017

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.

@tal-beja

This comment has been minimized.

Copy link

tal-beja commented Dec 25, 2017

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

@enriquejr99

This comment has been minimized.

Copy link

enriquejr99 commented Mar 30, 2018

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.

@wuya666

This comment has been minimized.

Copy link

wuya666 commented Aug 21, 2018

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?

@enriquejr99

This comment has been minimized.

Copy link

enriquejr99 commented Aug 21, 2018

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

@nczhu

This comment has been minimized.

Copy link

nczhu commented Nov 21, 2018

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

@se3000

This comment has been minimized.

Copy link

se3000 commented Nov 21, 2018

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

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