Skip to content

ERC827 Token Standard (ERC20 Extension) #827

Closed
@AugustoL

Description

@AugustoL
EIP: 827
Title: ERC827 Token Standard (ERC20 Extension)
Author: Augusto Lemble <me@augustol.com>
Type: Token Standard
Status: Draft
Category: ERC
Created: 2018-01-11
Updated: 2019-05-21

This standard is still a draft and is proven to be unsafe to be used

Simple Summary

A extension of the standard interface ERC20 for tokens with methods that allows the execution of calls inside transfer and approvals.

Abstract

This standard provides basic functionality to transfer tokens, as well as allow tokens to be approved so they can be spent by another on-chain third party. Also it allows to execute calls on transfers and approvals.

Motivation

This extension of the ERC20 interface allows the token to execute a function in the receiver contract contract after the approval or transfer happens. The function is executed by the token proxy, a simple proxy which goal is to mask the msg.sender to prevent the token contract to execute the function calls itself.
The ERC20 token standard is widely accepted but it only allows the transfer of value, ethereum users are available to transfer value and data on transactions, with these extension of the ERC20 token standard they will be able to do the same with ERC20 tokens.

I saw a lot of new standards being proposed in the community and I think the way to improve the current ERC20 standard is with an extension that is fully compatible with the original standard and also add new methods, but keeping it simple at the same time, the code to be added to the ERC20 standard is near 150 lines of code.

When to use each function

  • approveAndCall: Probably the one that you will need, maybe the only one since it only allows the receiver contract to use approved balance. The best practice is to check the allowance of the sender and then do your stuff using the transferFromAndCall method.

  • transferAndCall: There is no way to check that the balance that will be transferred is the correct one, this function is useful when a function dont need to check any transfer of value.

  • transferFromAndCall: Same as transferAndCall, only useful when there is no need to check the transfered amount of tokens and want to spend approved balance.

Specification

Token

Methods

NOTE: Callers MUST handle false from returns (bool success). Callers MUST NOT assume that false is never returned!

name - ERC20

Returns the name of the token - e.g. "MyToken".

OPTIONAL - This method can be used to improve usability,
but interfaces and other contracts MUST NOT expect these values to be present.

function name() constant returns (string name)

symbol - ERC20

Returns the symbol of the token. E.g. "HIX".

OPTIONAL - This method can be used to improve usability,
but interfaces and other contracts MUST NOT expect these values to be present.

function symbol() constant returns (string symbol)

decimals - ERC20

Returns the number of decimals the token uses - e.g. 8, means to divide the token amount by 100000000 to get its user representation.

OPTIONAL - This method can be used to improve usability,
but interfaces and other contracts MUST NOT expect these values to be present.

function decimals() constant returns (uint8 decimals)

totalSupply - ERC20

Returns the total token supply.

function totalSupply() constant returns (uint256 totalSupply)

balanceOf - ERC20

Returns the account balance of another account with address _owner.

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

transfer - ERC20

Transfers _value amount of tokens to address _to, and MUST fire the Transfer event.
The function SHOULD revert if the _from account balance does not have enough tokens to spend.

A token contract which creates new tokens SHOULD trigger a Transfer event with the _from address set to 0x0 when tokens are created.

Note Transfers of 0 values MUST be treated as normal transfers and fire the Transfer event.

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

transferFrom - ERC20

Transfers _value amount of tokens from address _from to address _to, and MUST fire the Transfer event.

The transferFrom method is used for a withdraw workflow, allowing contracts to transfer tokens on your behalf.
This can be used for example to allow a contract to transfer tokens on your behalf and/or to charge fees in sub-currencies.
The function SHOULD revert unless the _from account has deliberately authorized the sender of the message via some mechanism.

Note Transfers of 0 values MUST be treated as normal transfers and fire the Transfer event.

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

approve - ERC20

Allows _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.

Users SHOULD make sure to create user interfaces in such a way that they set the allowance first to 0 before setting it to another value for the same spender.
THOUGH The contract itself shouldn't enforce it, to allow backwards compatibility with contracts deployed before

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

allowance - ERC20

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

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

ERC827 Proxy

A very simple proxy contract used to forward the calls form the token contract.

  • The proxy is deployed and assigned on the ERC827 token on the ERC827 constructor.
  • The proxy can only execute calls coming form the token contract.

There is a public variable called proxy in the ERC827 token, this can be used to check if the call is coming from the ERC827 token since the proxy can only forward calls from the token contract.

contract ERC827Proxy {

  address public token;
  bytes4 public callContractFunctionSignature = bytes4(
    keccak256("callContract(address,bytes)")
  );

  constructor() public {
    token = address(msg.sender);
  }

  function callContract(
    address _target, bytes memory _data
  ) public payable returns (bool) {
    require(
      msg.sender == address(token),
      "Proxy cant execute calls to the token contract"
    );
    (bool success, bytes memory data) = _target.call.value(msg.value)(_data);
    require(success, "Proxy call failed");
    return true;
  }

}

ERC827 methods

transferAndCall - ERC827

Execute a function on _to with the _data parameter, if the function ends successfully execute the transfer of _value amount of tokens to address _to, and MUST fire the Transfer event.

This method is payable, which means that ethers can be sent when calling it, but the transfer of ether needs to be handled in the call is executed after transfer since the one who receives the ether is the token contract and not the token receiver.

The function SHOULD revert if the call to _to address fails or if _from account balance does not have enough tokens to spend.
The ERC20 transfer method is called before the _call(_to, _data).

Note Transfers of 0 values MUST be treated as normal transfers and fire the Transfer event.

Important Note Do not use this method with fallback functions that receive the value transferred as parameter, there is not way to verify how much value was transferred on the fallback function.

  function transferAndCall(
    address _to, uint256 _value, bytes memory _data
  ) public payable returns (bool) {
    super.transfer(_to, _value);
    _call(_to, _data);
    return true;
  }

transferFromAndCall - ERC827

Execute a function on _to with the _data parameter, if the function ends successfully execute the transfer of _value amount of tokens from address _from to address _to, and MUST fire the Transfer event.

This method is payable, which means that ethers can be sent when calling it, but the transfer of ether needs to be handled in the call is executed after transfer since the one who receives the ether is the token contract and not the token receiver.

The transferFromAndCall method is used for a withdraw workflow, allowing contracts to transfer tokens on your behalf before executing a function.
The ERC20 transferFrom method is called before the _call(_to, _data).
This can be used for example to allow a contract to transfer tokens on your behalf and/or to charge fees in sub-currencies.
The function SHOULD revert if the call to _to address fails or if the _from approved balance by _from to msg.sender is not enough to execute the transfer.

Note Transfers of 0 values MUST be treated as normal transfers and fire the Transfer event.

Important Note Do not use this method with fallback functions that receive the value transferred as parameter, there is not way to verify how much value was transferred on the fallback function.

  function transferFromAndCall(
    address _from, address _to, uint256 _value, bytes memory _data
  ) public payable returns (bool) {
    super.transferFrom(_from, _to, _value);
    _call(_to, _data);
    return true;
  }

approveAndCall - ERC827

Execute a function on _spender with the _data parameter, if the function ends successfully allows _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.

This method is payable, which means that ethers can be sent when calling it, but the transfer of ether needs to be handled in the call is executed after transfer since the one who receives the ether is the token contract and not the token receiver.

Clients SHOULD make sure to create user interfaces in such a way that they set the allowance first to 0 before setting it to another value for the same spender.
The ERC20 approve method is called before the _call(_spender, _data).
The function SHOULD revert if the call to _spender address fails.
THOUGH The contract itself shouldn't enforce it, to allow backwards compatibility with contracts deployed before

  function approveAndCall(
    address _spender, uint256 _value, bytes memory _data
  ) public payable returns (bool) {
    super.approve(_spender, _value);
    _call(_spender, _data);
    return true;
  }

Events

Transfer - ERC20

MUST trigger when tokens are transferred, including zero value transfers.

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

Approval - ERC20

MUST trigger on any successful call to approve(address _spender, uint256 _value).

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

Past Issues

The main issue that has been recognized by the community is that the standard does not follow the assumption about executing calls in behalf of a token contract, every smart contract that handle token balances assume the token contract will execute only the common methods and maybe a callback that is implemented by the token itself. This standard break that rule and allow the execution of arbitrary calls making it hard to integrate in current solutions.
UPDATE
This was solved by adding a simple proxy to the token and forwarding the calls coming from the token contract, the proxy ensure that the calls come only from the token contract and allows this to be verified on chain, this prevents the token address to be used as msg.sender allowing the integration with current solutions.

Discussion channel

https://gitter.im/ERC827

Revisions

  • 2019/02/19: Simple proxy integrated in token contract to forward calls to any other contract.
  • 2018/06/28: Changed implementation link in zeppelin-solidity for windingtree/erc827 repository.
  • 2018/06/27: Added warning, current issued of the standard and public channel link.
  • 2018/04/17: Rename of functions to avoid function overloading and added payable modifier to ERC827 functions.
  • 2018/02/13: Added CC0 copyright
  • 2018/02/13: Added complete function code and notes abouts usage of each function
  • 2018/01/11: Initial Draft

Implementation

ERC827 Interface in Winding Tree

[ERC827 Standard Token implementation in Winding Tree](https://github.com/windingtree/erc827/blob/master/contracts/ERC827/ERC827.sol

Copyright

Copyright and related rights waived via CC0

Activity

changed the title [-]ERC826 token standard[/-] [+]ERC827 token standard[/+] on Jan 11, 2018
changed the title [-]ERC827 token standard[/-] [+]ERC827 Token Standard (ERC20 Extension)[/+] on Jan 11, 2018
spalladino

spalladino commented on Jan 11, 2018

@spalladino
Contributor

I really like the fact that it remains fully ERC20 compatible, and does not force receivers of the token to implement a certain function to signal that they accept them.

One question though: what is the rationale behind making the external call before the transfer or approval? I would have expected the call to occur afterwards, so the receiver of the tokens can check the transferred/approved balance and act upon it on that same call. And by throwing if the call goes wrong, the transfer gets rolled back all the same.

AugustoL

AugustoL commented on Jan 11, 2018

@AugustoL
Author

@spalladino In the approveData method the call is executed after the approve and in the transfer methods the call is executed before the transfer. At first all calls were executed before the ERC20 methods, but I changed the order in the approveData because I think the next call to be executed after an approve will want to have the amount to be transfered already approved, doing this you can execute a transferFrom inside the call and if all the things you wanted to do and check went fine you claim the tokens.

I think the order of the function call in approveData is not explained correctly in the issue, Im going to update it.

You are right that if the call fails the transfer and transferFrom will be reverted, the order can be changed and it will have the same effect I guess.

I have no problem on changing the order :)

spalladino

spalladino commented on Jan 11, 2018

@spalladino
Contributor

Oops sorry, I misunderstood the order in approveData. As for the order in transferData and transferDataFrom, I'd follow the same approach of transfer-then-call as in approve, for the same reasons you mention. WDYT?

AugustoL

AugustoL commented on Jan 11, 2018

@AugustoL
Author

I agree, im going to change the description in the issue.

shrugs

shrugs commented on Jan 11, 2018

@shrugs

I like this. Where do you envision this standard relative to ERCs 20, 223, and 777? It seems like 827 is a fantastic stopgap that's 20-compatible that should be adopted relatively quickly before the community gets around to ratifying 223 or 777.

Thoughts?

facuspagnuolo

facuspagnuolo commented on Jan 11, 2018

@facuspagnuolo

I really like this, thanks a lot! What do you think about overloading transfer, approve and transferFrom functions considering the new bytes _data argument?

AugustoL

AugustoL commented on Jan 11, 2018

@AugustoL
Author

@shrugs yes I agree, less code is safer and easier to test, and I think this new methods fulfill the needs that the rest of the standards are trying to cover.

@facuspagnuolo but if we do that we will change the ERC20 function signatures and it wont be ERC20 compatible anymore, right? I think we cant have functions with the same name and different arguments, or we can? 🤔

facuspagnuolo

facuspagnuolo commented on Jan 11, 2018

@facuspagnuolo

@AugustoL as far as I know, if we provide an overloaded function changing the amount of parameters, I think it won't change the signature of the ERC20 functions, for example:

  • ERC20 transfer function will keep being: keccak256("transfer(address,uint256)")
  • ERC827 transfer function will be: keccak256("transfer(address,uint256,bytes)")

am I right?

AugustoL

AugustoL commented on Jan 11, 2018

@AugustoL
Author

@facuspagnuolo I think you are, let me do a quick test! 👀

facuspagnuolo

facuspagnuolo commented on Jan 11, 2018

@facuspagnuolo

Another quick thing, we might want to distinguish between throw and revert in the spec

AugustoL

AugustoL commented on Jan 11, 2018

@AugustoL
Author

@facuspagnuolo I changed the method names to be the same as ERC20 but with the data argument and the signature is different so there is not issue on having the same name but with the _data argument.

abandeali1

abandeali1 commented on Jan 19, 2018

@abandeali1

I think that allowing the token contract to call any function at any address is much less useful than calling some sort of tokensReceived method on the receiving contract (as in ERC223/777). Receiving contracts have no way of knowing that the calling address is a token contract, so they can't really do anything useful with the passed in data.

A big part of this is that msg.sender will always be the token address, where a tokensReceived method knows the intended sender from the passed in arguments. There is also no explicit way to link the token transfer to the function call, which really limits the possibilities.

It also seems like it could have unintended consequences to allow a token to call any arbitrary data. For example, if tokenA is accidentally transferred to an ERC827 token contract, anyone would be able to claim these tokens by simply calling tokenA.transfer(self, value) from the ERC827 token.

AugustoL

AugustoL commented on Jan 20, 2018

@AugustoL
Author

@abandeali1 thanks for the feedback! But I think this is more useful because you dont need to have any specific method in the receiver contract to call it, but if the contract has it you can still use it, so you can call any other contract and make use of the tokenFallback function. In fact I think we can say that this token is also ERC223-Receiver compatible ?

There is way to know who executed the transaction, you can send a signature of the msg.sata as a parameter and verify the signer in the contract that is receiving the call form the token. you can also use tx.origin but I understand that is not recommended.

And if there is tokens transfered to this contract they can be claimed by anyone, I dont see any problem there, at least they are not stuck there, which I think is the main problem, having tokens that are not able to being used.

145 remaining items

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @strotter@fulldecent@spalladino@frangio@ptrwtts

        Issue actions

          ERC827 Token Standard (ERC20 Extension) · Issue #827 · ethereum/EIPs