Ethereum Tutorials

ERC20 Contracts Tutorial

ERC20 is the most popular standard for contracts, it allows you to create your own "tokens", "coins" or "shares" in an Ethereum block chain. Of course, some experts would point out that coins are different, and explain how... Ignore it, as the terminology is still too fuzzy to waste time on it.

This tutorial is for people confident in Solidity programming language. Also you need to know standard tips and tricks used to create safe and robust Smart Contracts. On our site you will find beginner-to-advanced tutorials to help you learn all this.

Standard code blocks of a typical ERC20 Contract

As ERC20 contract is a standard, it is not surprising at all that there are some blocks of code that are copied from one contract to another with little to no changes. Usually in programmers' community the "copypaste" approach is considered a bad tone, however here it is justified.

Code blocks mentioned above are well tested, which is very important, considering highest security requirements for smart contracts, much higher then are applied to regular software.

This is why developers prefer using code that has a history of safe usage, rather than reinventing the wheel.

ERC20Interface

An ERC contract should implement few functions, that we are going to move outside, as an interface:

contract ERC20Interface
{
	function totalSupply() public constant returns (uint totalTokenCount);
	function balanceOf(address _owner) public constant returns (uint balance);
	function transfer(address _to, uint _value) public returns (bool success);
	function transferFrom(address _from, address _to, uint _value) public returns (bool success);
	function approve(address _spender, uint _value) public returns (bool success);
	function allowance(address _owner, address _spender) public constant returns (uint remaining);
	event Transfer(address indexed _from, address indexed _to, uint _value);
	event Approval(address indexed _owner, address indexed _spender, uint _value);
}

Functions like totalSupply and balanceOf do not require any explanations.
Function transfer(address _to, uint _value) transfers funds (in tokens, not in ether) from one address to another. A function is public and not protected by onlyOwner modifier (see below). What if someone calls it: will he be able to get hold on tokens that do not belong him?

Note that this is an INTERFACE, functions are declared but not implemented. In other words, it is up to you to derive a class from this declaration and to fill it with actual code.

Ownable

Having an owner in contracts like this is an absolute must, we need to restrict access to certain operations to the owner only, or anyone will be able to take our virtual treasure from us. The following contract is de-facto standard for keeping tract of the ownership, for historical reasons it can be called either "owned" or "ownable":

contract Ownable 
{
    address public m_addrOwner;

    function Ownable() public
    {
        m_addrOwner = msg.sender;
    }

    // ------
    
    modifier onlyOwner() 
    { 
        require(msg.sender == m_addrOwner);   
        _;
    }

    // ------
    
    function transferOwnership(address newOwner) public onlyOwner 
    {
        require(newOwner != address(0));      
        m_addrOwner = newOwner;
    }
}

Basically, this contract allows you to restrict an access to the contract to its creator: m_addrOwner is set in a constructor, and a constructor can only be called once. Also, it allows (to the current owner only!) to pass the ownership to another address.

Name, Symbol, Decimals

Here is another "highly recommended" piece of our code: we need to tell, to online exchanges, what the name (in human readable form) and symbol (in short form) of our contract should be, in other words, under what name to list it.

string public constant symbol = "SHT";
string public constant name = "ShareHolder Token";
uint8 public constant decimals = 18;					

Few words about "decimals". Token (usually in its parent class) has a "decimals" field specifying the number of decimal points to show to the user. For example, if you make coins (like Ethereum itself) you may want to allow it to have 18 digits after the decimal "dot" (like Ethereum does). While if you are selling movie tickets, the decimals should be zero, as there is no such thing as 1.235 movie tickets.

Decimals are used by online exchanges to display data properly: it is FOR HUMANS only, for our convenience.

SafeMath

One of the most irritating aspects of Solidity is probably the way it handles negative numbers. Most of its operations are done with unsigned integers! So if you accidentally subtract (uint) 2 from (uint) 1, you will get an overflow: a HUGE number instead of the negative result.

Now imagine that you have a variable holding user's balance. And as the user requests withdrawals, this balance goes down:

nBalance -= nWithdrawAmount;

As soon as the user asks for more money than he has, an overflow happens (well, unless you do checking) and now he has an unlimited balance. So if your contract has other peoples' money... and that user asks again... he'll get it all, as his balance is HUGE.

To handle this scenario, as well as few similar ones, a SafeMath library was created. It simply throws an exception if an overflow is about to happen, a transaction rolls back, and disaster is being prevented.

library SafeMath 
{
    function mul(uint256 a, uint256 b) internal pure returns (uint256) 
    {
        uint256 c = a * b;
        assert(a == 0 || c / a == b);
        return c;
    }

    function div(uint256 a, uint256 b) internal pure returns (uint256) 
    {
        uint256 c = a / b;
        return c;
    }

    function sub(uint256 a, uint256 b) internal pure returns (uint256) 
    {
        assert(b <= a);
        return a - b;
    }

    function add(uint256 a, uint256 b) internal pure returns (uint256) 
    {
        uint256 c = a + b;
        assert(c >= a);
        return c;
    }
}

For a reason that I can not fully comprehend (probably due to people being obsessed with "copypaste" programming pattern) this library includes "div" function. It does nothing, but people still include it. One would suggest adding divide by zero verification there, but Solidity automatically throws when dividing by 0 is attempted.

ERC20Token

As was mentioned above, the Interface should be implemented, filled with the code. Here is an implementation generated by ICO Generator, just keep in mind that there are few "extra" intermediate classes that people sometimes implement between ERC20Interface and ERC20Token, like BasicToken and so on. ICO Generator places all their code in ERC20Token:

contract ERC20Token is ERC20Interface
{
    using SafeMath for uint256;

	// Keep a Solidity "mapping" for users' balances
    mapping(address => uint256) m_mapBalances;
	
	// To transfer tokens, we need to get permission. This is an "escrow", holding promises like
	// if you give me X, I give you Y.
    mapping (address => mapping (address => uint256)) m_mapAllowed;
    
    // Transfer tokens for a specified address
    function transfer(address _to, uint256 nNumOfTokens) public returns (bool) 
    {
        require(_to != address(0) && nNumOfTokens > 0 && nNumOfTokens <= m_mapBalances[msg.sender]);
        
        m_mapBalances[msg.sender] = m_mapBalances[msg.sender].sub(nNumOfTokens);
        m_mapBalances[_to] = m_mapBalances[_to].add(nNumOfTokens);
        Transfer(msg.sender, _to, nNumOfTokens);
        return true;
    }
    
    // ------
    // Gets the balance (uint256 Tokens) of the specified address.
    function balanceOf(address addr) public constant returns (uint256 ) 
    {
        return m_mapBalances[addr];
    }
    
    // ------
    
    function transferFrom(address _from, address _to, uint256 _value) public returns (bool)
    {
        require(_to != address(0) &&
			m_mapAllowed[_from][msg.sender] >= _value && 
			m_mapBalances[_from] >= _value && 
			_value > 0);
        
        m_mapBalances[_from] = m_mapBalances[_from].sub(_value);
        m_mapBalances[_to] = m_mapBalances[_to].add(_value);
        m_mapAllowed[_from][msg.sender] = m_mapAllowed[_from][msg.sender].sub(_value);
        Transfer(_from, _to, _value);
        return true;
    }

    // ------
    
    // Aprove the passed address to spend the specified amount of tokens on behalf of msg.sender.
    function approve(address _spender, uint256 _value) public returns (bool) 
    {
        // To change the approve amount you first have to reduce the addresses`
        //  allowance to zero by calling `approve(_spender,0)` if it is not
        //  already 0 to mitigate the race condition described here:
        //  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729    
		
        require((_value == 0) || (m_mapAllowed[msg.sender][_spender] == 0));
        m_mapAllowed[msg.sender][_spender] = _value;
        Approval(msg.sender, _spender, _value);
        return true;
    }

    // ------
    // Check the amount of tokens that an owner allowed to a spender.
    function allowance(address _owner, address _spender) public constant returns (uint256 remaining) 
    {
        return m_mapAllowed[_owner][_spender];
    }    
}

First of all, you can see

using SafeMath for uint256;

Now, Solidity has many sizes of integer, from one to 256, but (at least at the time of this writing) they all are internally represented as uint256. So do not worry when your boolean value uses uint256 - there is nothing you can do.

The following mapping (Solidity key-value map) is used to keep a list of users (by an address) and their balances.

							
mapping(address => uint256) m_mapBalances;

Mapping allows to find a balance by the key, so if a person (an address) asks for a balance, it is easily available.

It takes two people to trade, and they should trust each other. However, they usually don't. So we can not simply say "you give me your money, and I will give you my tokens". What if I take money and give nothing in exchange?

To handle this situation, an intermediate person (exchange contract) is added, one both parties trust. It has access both to tokens of one side and to money of the other side.

To transfer tokens, we need to get permission. This is an "escrow", holding promises like if you give me X, I give you Y:

							
mapping (address => mapping (address => uint256)) m_mapAllowed;

To transfer tokens for a specified address, we call the following function:

function transfer(address _to, uint256 nNumOfTokens) public returns (bool) 
{
    require(_to != address(0) && nNumOfTokens > 0 && nNumOfTokens <= m_mapBalances[msg.sender]);
        
    m_mapBalances[msg.sender] = m_mapBalances[msg.sender].sub(nNumOfTokens);
    m_mapBalances[_to] = m_mapBalances[_to].add(nNumOfTokens);
    Transfer(msg.sender, _to, nNumOfTokens);
    return true;
}

Note that we use "add" and "sub" instead of "plus" and "minus", these are functions from SafeMath class.

The following function returns the balance (uint256 Tokens) of the specified address:

function balanceOf(address addr) public constant returns (uint256 ) 
{
    return m_mapBalances[addr];
}

The function does not change anything in the block chain, and therefore declared constant. It means, among other things, that we don't have to pay for calling it.

Earlier we saw the transfer function, used to directly move funds from one account to another. The following function uses escrow, it requires that transfer was allowed, in other words, it uses an intermediate party to secure the exchange:

function transferFrom(address _from, address _to, uint256 _value) public returns (bool)
{
    require(_to != address(0) &&
		m_mapAllowed[_from][msg.sender] >= _value && 
		m_mapBalances[_from] >= _value && 
		_value > 0);
        
    m_mapBalances[_from] = m_mapBalances[_from].sub(_value);
    m_mapBalances[_to] = m_mapBalances[_to].add(_value);
    m_mapAllowed[_from][msg.sender] = m_mapAllowed[_from][msg.sender].sub(_value);
    Transfer(_from, _to, _value);
    return true;
}

The allowance used in transferFrom is set in the following function. It aproves the passed address to spend the specified amount of tokens on behalf of msg.sender.

function approve(address _spender, uint256 _value) public returns (bool) 
{
    require((_value == 0) || (m_mapAllowed[msg.sender][_spender] == 0));
    m_mapAllowed[msg.sender][_spender] = _value;
    Approval(msg.sender, _spender, _value);
    return true;
}

To change the approve amount you first have to reduce the addresses' allowance to zero by calling `approve(_spender,0)` if it is not already 0 to mitigate the race condition described here: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 This is where require((_value == 0)... came from.

This function is used to check the amount of tokens that an owner allowed to a spender:

							
function allowance(address _owner, address _spender) public constant returns (uint256 remaining) 
{
    return m_mapAllowed[_owner][_spender];
} 

Mintable

There are two major approaches to issuing tokens during the Crowd Funding. First, we initially generate maximum possible amount of tokens, and distribute whatever we can during the crowd sale. When crowd sale ends, we "burn" undistributed tokens.

The second approach, one used in ICO Generator tool, is to have no tokens at the beginning of a fund rising event, and to generate (or MINT) tokens as they are purchased. It means that at the end we don't have to "burn" anything.

Both methods work equally well, so it is a mater of personal preferences. Here is a Mintable token, derived from ERC20Token and Ownable:

contract MintableToken is ERC20Token, Ownable 
{
    uint256 public m_nTotalSupply;            // Amount currently minted
    bool public m_bMintingFinished = false;    // No more tokens from now on
    
    event Mint(address indexed to, uint256 amount);
    event MintFinished();

    modifier canMint() 
    {
        require(!m_bMintingFinished);
        _;
    }

    function totalSupply() constant public returns (uint256) { return m_nTotalSupply; }
    
    // ------
    
    function mint(address _to, uint256 _amount) public onlyOwner canMint returns (bool) 
    {
        m_nTotalSupply = m_nTotalSupply.add(_amount);
        m_mapBalances[_to] = m_mapBalances[_to].add(_amount);
        Mint(_to, _amount);
        return true;
    }

    // ------
    
    function finishMinting() onlyOwner public returns (bool) 
    {
        m_bMintingFinished = true;
        MintFinished();
        return true;
    }
}

As you can see, Mintable token can add records to "mapping" that was created in a parent class.

MyToken

Finally, we have to create "our" token, one that will actually be used in a crowd funding event. As we have implemented all required functionality already, the class is empty, all it does is introducing its class name.

contract MyToken is MintableToken 
{
}

Of course, nothing prevents you from renaming "MyToken" to anything you want.

Non-standard parts

This is pretty much all you need to know about ERC20 tokens. There are, of course, hand made changes that you can see in contract with published code (browse etherscan.io for "verified contracts". For example, you may see something like this (in a token!):

owner = 0x774b64894d979895cbb8934a9c8a8e88c43e8a01;

Obviously, this is not part of a standard, and you have to (if you need) figure out why it was done. In a particular case, my theory is, a programmer had to deploy client's contract, but client didn't trust him enough, so an address had to be hardcoded... maybe... Or maybe the owner is an existing contract... which still does not give us any clue why "transferOwnership" was not used. By the way, some contracts you can see onchain are far from perfection, so maybe there is no logical explanation to that puzzle at all.







Visibility is very important for this site. If you like it please link from your page to this URL or share info using Social Buttons below.
(C) snowcron.com, all rights reserved

Please read the disclaimer