ERC Token Standards for Dummies, Like Me
By: Ayush Tiwari
Tags: Token Sale, Smart Contracts, Token, Ethereum, Erc20

Ethereum based tokens which can be bought, sold, or traded, have become the backbone of Ethereum ecosystem, innovative companies use them as internal currencies within their ecosystem. Some are listed on exchanges and act like shares of the company and some become collectibles, as in the game of CryptoKitties. But still people aspiring to join the crypto race, are troubled by questions like these:

Which type of Token best suits my idea?

How many types of token standards are circulating in the Ethereum community?

What are the advantages and disadvantages of various ERC token standards?

How different token standards work?

If these questions linger in your mind too, or otherwise you’re just a scholar aiming to expand your knowledge, here is a technical introduction to various ERC standards popular in the Ethereum community. But, do you know what is ERC?

KEEP IN MIND BEFORE YOU PROCEED: The code present in this blog is solely for educational purposes and not written in its entirety. Please do not implement it in production applications!

First, what is ERC?

ERC stands for Ethereum Request for Comment. Every open source community needs a proposal request-approval system to accept revolutionary changes put forward by its users and improve itself. ERCs serve this purpose for Ethereum, they include some technical guidelines elaborating suggestions about development of the Ethereum network.

Development is a step by step process

Developers of the Ethereum community propose new standards for the Ethereum platform, by submitting an Ethereum Improvement Proposal (EIP). This includes protocol specifications and contract standards. Once that EIP is approved by a committee and finalized, it becomes an ERC.

The finalized EIPs give the Ethereum developers a set of implementable standards. This allows Smart Contracts to be built with these standards, which a common interface can access.

My Assumptions

After this part of the blog I’m going to make some assumptions, while explaining token anatomy concepts, if you don’t, you should skip the anatomy part for each token standard, they will be as follows:

Reader should have basic knowledge of Solidity’s syntax. Or at least you should be a developer who could relate to it. You are able to refer to a subject explained in a previous section of this blog and relate it to the current subject to further your understanding. You know about standard zeppelin contracts, if you don’t you should be able to browse the following Github handle to understand my references:

ERC 20

ERC 20 is the most well-known among all the standards present within the entire crypto community, and most tokens issued on top of the Ethereum platform use it. Helping developers to accurately predict how new tokens will function within the larger Ethereum system.

The impact that ERC 20 therefore has on developers is massive, as projects do not need to be redone each time a new token is released. Rather, they are designed to be compatible with new tokens, provided those tokens adhere to the rules. Developers of new tokens have by-and-large observed the ERC 20 rules, meaning that most of the tokens released through Ethereum based ICOs are ERC 20 compliant.

ERC 20 Anatomy

Here’s a basic interface describing function and event signatures of ERC20 contracts, which is followed by the explanation of each function:

contract ERC20 {

event Transfer(

address indexed from,

address indexed to,

uint256 value

);



event Approval(

address indexed owner,

address indexed spender,

uint256 value

); function totalSupply() public view returns(uint256);



function balanceOf(address who) public view returns(uint256);



function transfer(address to, uint256 value) public returns(bool);



function allowance(address owner, address spender)

public view returns (uint256);



function transferFrom(address from, address to, uint256 value)

public returns (bool);



function approve(address spender, uint256 value)

public returns (bool);

}

totalSupply

Public function totalSupply , is accessible to all, and it displays the number of tokens in circulation. Since, it is labelled with a view modifier, calling it won’t consume any gas. So you can know about the total tokens in supply for any ERC 20 based token, for free. Zeppelin implements this logic by updating the value of internal variable totalSupply_ every time new tokens are minted. Its value can be accessed as follows:

// its value is increased when new tokens are minted

uint256 totalSupply_; // access the value of totalSupply_

function totalSupply() public view returns (uint256) {

return totalSupply_;

}

balanceOf

Function balanceOf is another public function with view modifier making it accessible to all and gas free. It takes in an Ethereum addresses and returns the number of tokens allocated to that address. Zeppelin maintains an internal mapping for this purpose, as follows:

// Updated when tokens are minted or transferred

mapping(address => uint256) balances; // Returns tokens held by the address passed as _owner

function balanceOf(address _owner)

public view returns (uint256 balance) {

return balances[_owner];

}

transfer

Function transfer is different from the above two functions because it will consume gas, since it will result in a state change within the Ethereum Smart contracts. It is used to transfer tokens from one address to another, when called by respective token holders. _to address is the recipient of the tokens of value amount, in the following representation taken from Zeppelin:

function transfer(address _to, uint256 _value)

public returns (bool) { // Check for blank addresses

require(_to != address(0)); // Check to ensure valid transfer

require(_value <= balances[msg.sender]); // SafeMath.sub will throw if there is not enough balance.

balances[msg.sender] = balances[msg.sender].sub(_value);

balances[_to] = balances[_to].add(_value);



// Event transfer defined in the ERC 20 interface above

Transfer(msg.sender, _to, _value); return true;

}

allowance, approve and transferFrom

Lastly, functions allowance , approve and transferFrom offer some advanced functionalities and are used to authorize some other Ethereum address to utilize your tokens on your behalf. This other Ethereum address could be a smart contract designed to handle tokens or just another account. Following is the implementation by Zeppelin solidity, incorporating the required logic for these functions:

Function approve is used by a token owner, to allow some spender to use a particular value of his tokens on his behalf.

is used by a token owner, to allow some to use a particular of his tokens on his behalf. Similarly, allowance can be used to check spender's allowance for a particular owner

can be used to check allowance for a particular And transferFrom can be finally used by spender to transfer allowed number of tokens.

What was the problem with ERC 20?

Now that you have gone through the basic functionalities of ERC 20, we can start discussing it flaws and the different requirements which resulted in various flavors of token standards present in the Ethereum driven market.

To be true, there were many. Not that ERC 20 has many flaws, but it is the most basic standard for tokens, and to solve specific problems, many upgrades were needed. Here we will first discuss various improvements suggested by ERC223.

ERC 223

ERC223 is built on top of ERC20 and is backwards compatible, meaning it incorporates all the functions mentioned above in ERC 20 section, it solves the following problems:

Problem of lost tokens

It happens during the transfer of ERC 20 tokens to a contract. When people mistakenly use the instructions for sending tokens to a wallet and send them to a smart contract which is not designed to handle it, their tokens get stuck in the smart contract. What’s worse? Since, the contract was not designed to handle these tokens, these tokens will be stuck there forever.

Here is an explanation of the problem from ERC 223 official github page:

Reducing energy consumption

The transfer of ERC223 tokens to a contract is a one step process rather than two steps process (for ERC20), and this means two times less gas and no extra Blockchain bloating.

Anatomy of ERC 223

After understanding the problems solved by ERC 223, let’s look into a simple implementation to understand how it works:

contract ERC223 {

event Transfer(

address indexed from,

address indexed to,

uint value,

bytes indexed data

); function totalSupply() public view returns(uint256); function balanceOf(address _owner) public view returns(uint256); function transfer(address _to, uint256 _value)

public returns (bool); function transfer(address to, uint value, bytes data)

public returns (bool);

}

ERC223ReceivingContract

Standard ERC223ReceivingContract must have a function tokenFallback , the sample interface would be like this:

contract ERC223ReceivingContract {

/**

* @dev Standard ERC223 function that will handle

* incoming token transfers.

* @param _from Token sender address.

* @param _value Amount of tokens.

* @param _data Transaction metadata.

*/

function tokenFallback(

address _from,

uint _value,

bytes _data

) public;

}

transfer

Functions totalSupply and balanceOf have been discussed in ERC 20 the change comes in implementation of the new transfer function.

function transfer(address _to, uint _value, bytes _data)

public {



assembly {

codeLength := extcodesize(_to)

} if (codeLength > 0) {

ERC223ReceivingContract receiver = ERC223ReceivingContract(_to);

receiver.tokenFallback(msg.sender, _value, _data);

}



require(super.transfer(_to, _value));

}

In the above code, our new transfer function takes in an extra parameter compared to the one mentioned in ERC 20, i.e. _data . It is used by assembly to retrieve the size of the code on target address, for a contract address codeLength will be greater than zero and tokenFallback function will be invoked for it. The token transfer will fail if the recipient is a contract but does not implement the tokenFallback function or the fallback function to receive funds.

In short, ERC223 checks for byte code length of the address tokens are being transferred to, and if it is a contract, it looks for a function with the signature of tokenFallback , hence solving the problem of tokens stuck in contracts.

ERC 827

ERC 827 is a standard which rivals ERC 223, it can be used to solve the same problems solved by ERC 223, moreover it offers flexibility to transfer data along with tokens to smart contracts and execute them. This means it can be used to solve specific problems other than tokens getting stuck in smart contracts. In recent times ERC 827 seems to be winning against ERC 223 as it has been included by zeppelin in its open source contracts, while ERC 223 is still not present in the tokens provided by them. The motivation stated in ERC 827 ERC/EIP is as follows:

This extension of the ERC20 interface allows any tokens on Ethereum to be re-used by other applications: from wallets to decentralized exchanges. 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.

To understand the usage of ERC 827 token let’s think of a hypothetical shopping cart on a merchants website.

Shopping cart on a merchants website

This cart has items waiting to be bought stored in it. But it cannot proceed to checkout unless user has the required number of tokens. When someone transfers tokens to the owner of the cart, some data is passed with the cart, which calls another function present within a contract which checks whether the cart owner has enough balance and proceeds to checkout for the items saved in cart accordingly.

Anatomy of ERC 827

A sample implementation of the ERC 827 contracts implemented by Zeppelin is as follows:

contract ERC827 is ERC20 {

function approve(

address _spender,

uint256 _value,

bytes _data

) public returns (bool);



function transfer(

address _to,

uint256 _value,

bytes _data

) public returns (bool);



function transferFrom(

address _from,

address _to,

uint256 _value,

bytes _data

) public returns (bool);

}

The ERC827 contract here inherits the standard ERC20 contract. Hence becoming backwards compatible it updates the signature and logic behind only three functions of the ERC 20 contracts- approve , transfer and transferFrom . We will discuss them in detail one by one:

approve

It allows to approve the transfer of value and execute a call with the data sent during the approve function call.

Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender’s allowance to 0 and set the desired value afterwards:

function approve(address _spender, uint256 _value, bytes _data)

public returns (bool) {

require(_spender != address(this));



require(super.approve(_spender, _value));



require(_spender.call(_data));



return true;

}

transfer

It transfers tokens to a specified address and executes a call with the sent data on the same transaction. It will return true if the call function is executed successfully.

function transfer(address _to, uint256 _value, bytes _data)

public returns (bool) {

require(_to != address(this)); require(super.transfer(_to, _value)); require(_to.call(_data));

return true;

}

transferFrom

Like transfer function it also transfers tokens from one address to another and make a contract call on the same transaction. But this can be used in coordination with approve function and execute different logic while approving and transferring tokens.

function transferFrom(

address _from,

address _to,

uint256 _value,

bytes _data

) public returns (bool) {

require(_to != address(this));



super.transferFrom(_from, _to, _value);



require(_to.call(_data));

return true;

}

ERC 721

ERC 721 presents a totally different concept from ERC 223 and ERC 827. To understand it properly readers must grasp the concept of fungible and non-fungible items.

Difference between fungible and non fungible?

An item can be considered fungible if it can be exchanged with another item of same type. This means the value of two fungible items is same in everyone’s eyes and they’re interchangeable. Consider a 100 Rs bill.

Fungible 100 Rs

This 100 Rs is interchangeable with another 100 Rs bill with a different reference number. So, it is fungible.

Non-fungible Healey Westland

Non fungible comes from the word “not interchangeable”. Lets try to understand this with a Healey Westland, one of the rare collectible cars. The value of two different Healey Westland may vary on their age and conditions. They may vary from collector to collector. This makes Healey Westland a non-fungible item.

ERC 721 based Cryptokittes

Now, we’re ready to understand the usage of ERC 721, which portrays the concept of non-fungible tokens. You cannot differentiate between two ERC 20 based tokens, they’re one and the same for you, but ERC 721 proposes a concept by which token holders will be able to differentiate between the tokens they hold. One famous example is CryptoKitties, it is a game in which players can buy, sell, trade, and breed digital cats. Each cat is unique in some way and this uniqueness makes the CryptoKitties extremely collectible, as someone could take interest in the characteristics of several kittens and wish to own them.

However, there are various other cases too, when non-fungibility of tokens tokens, can prove to be an asset. For instance, tokens can be used to represent real estate objects, intellectual property rights, etc… And each token might have some different parameters added to it. Or they can be used to represent items on a merchant’s website where an Adidas shoe will be different from a Nike shoe. Even different shoes within the same brand can be represented in a similar fashion.

Adidas vs Nike

ERC 721 Anatomy

Now we know why we need ERC 721 tokens so lets understand how they work. Here is an interface of ERC 721 followed by Zeppelin, followed by the explanation of functions mentioned in the interface, as implemented by zeppelin.

contract ERC721 {

event Transfer(

address indexed _from,

address indexed _to,

uint256 _tokenId

); event Approval(

address indexed _owner,

address indexed _approved,

uint256 _tokenId

); function balanceOf(address _owner)

public view returns (uint256 _balance); function ownerOf(uint256 _tokenId)

public view returns (address _owner); function transfer(address _to, uint256 _tokenId) public; function approve(address _to, uint256 _tokenId) public; function takeOwnership(uint256 _tokenId) public;

}

balanceOf

In the following snippet ownedTokens represents the list of token IDs owned by a particular address. And balanceOf function returns the number of tokens owned by a particular address.

mapping (address => uint256[]) private ownedTokens; function balanceOf(address _owner) public view returns (uint256) {

return ownedTokens[_owner].length;

}

ownerOf

Mapping tokenOwner takes in tokenId and returns the owner of that ID, but since it's visibility is private, ownerOf function is used to make the value of this mapping available publicly. It also includes a check against zero addresses before returning the value.

mapping (uint256 => address) private tokenOwner; function ownerOf(uint256 _tokenId) public view returns (address) {

address owner = tokenOwner[_tokenId];

require(owner != address(0));

return owner;

}

transfer

This transfer function takes in the new owner’s address as _to parameter and _tokenId of the token being transferred, also note that it can only be called by the owner of token. It must include the logic to check whether the transfer clears approval check, required for a transfer. Then comes the logic to remove token’s possession from current owner and add it to the list of tokens owned by new owner.

modifier onlyOwnerOf(uint256 _tokenId) {

require(ownerOf(_tokenId) == msg.sender);

_;

} function transfer(address _to, uint256 _tokenId)

public onlyOwnerOf(_tokenId) {

// Logic to clear approval for token transfer // Logic to remove token from current token owner // Logic to add Token to new token owner

}

approve

Approves another address to claim for the ownership of the given token ID, this is another function restricted by a modifier onlyOwnerOf , which means only a token owner can access this function for a particular token. Variable tokenApprovals is a mapping from token ID to approved addresses. Similar to the transfer function discussed above approve too, takes in _to (the new token owner’s address) and _tokenId as input parameters. It checks new owner must not be the same as previous owner of the token then if any one of the addresses among _to and previously approved address passes the address(0) check, it updates the tokenApprovals mapping and fires an event named Approval .

mapping (uint256 => address) private tokenApprovals; modifier onlyOwnerOf(uint256 _tokenId) {

require(ownerOf(_tokenId) == msg.sender);

_;

} function approvedFor(uint256 _tokenId)

public view returns (address) {

return tokenApprovals[_tokenId];

} function approve(address _to, uint256 _tokenId)

public onlyOwnerOf(_tokenId) {

address owner = ownerOf(_tokenId);

require(_to != owner); if (approvedFor(_tokenId) != 0 || _to != 0) {

tokenApprovals[_tokenId] = _to; // Event initialised in the interface above

Approval(owner, _to, _tokenId);

}

}

takeOwnership

Function approvedFor has been explained in the previous section, function isApprovedFor returns a Boolean value depending upon the response of condition approvedFor(_tokenId) == _owner . Function takeOwnership takes in _tokenId and applies the same check on message sender, if he passes the check logic similar to the transfer function is applied to claim the ownership of given _tokenID .

function isApprovedFor(address _owner, uint256 _tokenId)

internal view returns (bool) {

return approvedFor(_tokenId) == _owner;

} function takeOwnership(uint256 _tokenId) public {

require(isApprovedFor(msg.sender, _tokenId)); // Logic to clear approval for token transfer // Logic to remove token from current token owner // Logic to add Token to new token owner

}

Summary

Ah, this must have been a pretty intense read. So, congratulations on reaching the end. Now you should be having some basic knowledge of the popular token standards circulating within the Ethereum community.

Congratulations!!!

For those who need to choose a particular Token standard for their application, please keep in mind that at the time of this writing most of the standards explained in this blog, i.e. ERC 223, ERC 721 and ERC 827 are still not accepted officially and may undergo revisions.

Sources