Skip to content

Tokens: ERC‐20

Ananthan edited this page May 25, 2024 · 7 revisions

The ERC-20 (Ethereum Request for Comments 20), was proposed by Fabian Vogelsteller in November 2015. This token standard implements an API for tokens within smart contracts.

ERC-20 token standard defines a set of rules that enables easy interaction in the network. The token standard is applicable to all ERC-20 tokens and it includes common rules like what is the total supply of token, how the tokens are transferred, etc. The ERC-20 standard contains 6 key functions that must be implemented to meet the standard.

If a smart contract implements the following methods and events it can be called an ERC-20 token contract and, once deployed, it will be responsible to keep track of the created tokens on Ethereum.

Methods in ERC-20

function name() public view returns (string)

function symbol() public view returns (string)

function decimals() public view returns (uint8)

function totalSupply() public view returns (uint256)

function balanceOf(address _owner) public view returns (uint256 balance)

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

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

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

function allowance(address _owner, address _spender) public view returns (uint256 remaining)

Events in ERC-20

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

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

Key Functions (Mandatory)

  • totalSupply() – Used to get the token supply of a specific ERC-20 token.
  • balanceOf() – This function allows a smart contract to store and return the balance of the provided address. The function accepts an address as a parameter, so it should be known that the balance of any address is public.
  • transfer() – Upon token creation, this function can send all the tokens to one wallet or distribute them to ICO (Initial Coin Offering) investors.
  • transferFrom() – Enables token holders to exchange tokens with one another after the initial distribution occurs.
  • approve() – Used to “approve” other accounts to withdraw a certain amount of tokens from the account calling the function.
  • allowance() – After approve() is used, allowance() is used to see the amount of tokens the approved account is allowed to withdraw from the original account.

Events

  • Approval - The Approval event is triggered whenever the approve() function is invoked.
  • Transfer - The Transfer event is triggered when the actual token transfer has occurred.

Optional Functions

Though these functions are not compulsory, developers often use this to improve the token usability.

  • Token Name - returns the name of the token
  • Symbol - returns the symbol representation (usually a short string like TKN)
  • Decimal (up to 18) - returns the number of decimal digits that the token can be divided into

ERC-20 Contract

Now let us take a look at the implementation of ERC-20 by OpenZeppelin. Let us start with the interface which defines the ERC-20 standard.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.1;

interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address who) external view returns (uint256);

    function allowance(address owner, address spender) external view returns (uint256);
    function transfer(address to, uint256 value) external returns (bool);
    
    function approve(address spender, uint256 value) external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

Now let us take a look at the implementation of ERC 20. First, we are going to inherit IERC20.

import "./IERC20.sol";

Next, we can define state variables and mappings used in this contract.

contract ERC20 is IERC20 {
    mapping (address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _allowances;
    uint256 private _totalSupply;
  
    string private _name;
    string private _symbol;
}

Using the _balances mapping, we can query token count using the owner's address. The _allowances mapping is used by the owner to provide a privilege to an account operating owner's tokens.

Next, we have a constructor for setting token name, token symbol, and mint tokens at the time of deployment.

constructor (string memory name, string memory symbol, uint8 decimal) public {
        _name = name;
        _symbol = symbol;
        _decimals = decimal;
        _mint(msg.sender, 10*10**10);
}

Next, let us go through the functions used in the ERC-20 contract implementation.

totalSupply

This function returns the number of tokens in existence.

function totalSupply() override public view returns (uint256) {
    return _totalSupply;
}

balanceOf

It returns the number of tokens owned by an account.

function balanceOf(address owner) override public view returns (uint256) {
    return _balances[owner];
}

allowance

The allowance function returns number of tokens that the spender is allowed to spend on behalf of the owner using transferFrom.

function allowance(address owner, address spender) override public view returns (uint256) {
    return _allowances[owner][spender];
}

transfer

This function transfer tokens from the caller's address to the recipient's address. It returns a boolean value indicating that function execution succeeded.

function transfer(address to, uint256 value) override public returns (bool) { require(value <= _balances[msg.sender]); require(to != address(0)); _balances[msg.sender] = _balances[msg.sender]-value; _balances[to] = _balances[to]+value; emit Transfer(msg.sender, to, value); return true; }

approve

It sets a specific amount of tokens as the allowance of spender over the caller's tokens. Returns true/false value indicating the operation's success.

function approve(address spender, uint256 value) override public returns (bool) {
    require(spender != address(0));
    _allowances[msg.sender][spender] = value;

    emit Approval(msg.sender, spender, value);

    return true;
}

transferFrom

This function transfers the number of tokens from sender to receiver using the allowance mechanism. Amount of tokens are deducted from the caller's allowance.

function transferFrom(address from, address to, uint256 value) override public returns (bool) {
    require(value <= _balances[from]);
    require(value <= _allowances[from][msg.sender]);

    require(to != address(0));
    _balances[from] = _balances[from]-value;
    _balances[to] = _balances[to]+value;
    _allowances[from][msg.sender] = _allowances[from][msg.sender]-value;

    emit Transfer(from, to, value);
    return true;
}

increaseAllowance

This function is used to increase the allowance granted to the spender by the caller.

function increaseAllowance( address spender, uint256 addedValue) public returns (bool) {
    require(spender != address(0));
    _allowances[msg.sender][spender] = (_allowances[msg.sender][spender] + addedValue);

    emit Approval(msg.sender, spender, _allowances[msg.sender][spender]);

    return true;
}

decreaseAllowance

This function is used to decrease the allowance granted to the spender by the caller.

function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
    require(spender != address(0));
    _allowances[msg.sender][spender] = (_allowances[msg.sender][spender] - subtractedValue);

    emit Approval(msg.sender, spender, _allowances[msg.sender][spender]);

    return true;
}

_mint

This function allows the creation of new tokens and assigns them to the account, increasing the total supply.

function _mint(address account, uint256 amount) internal {
    require(account != address(0));
    _totalSupply = _totalSupply + amount;
    _balances[account] = _balances[account] + amount;
    emit Transfer(address(0), account, amount);
}

_burn

You can use this function to remove a specific amount of tokens from circulation. It will reduce the total supply by specified amount.

function _burn(address account, uint256 amount) internal {
    require(account != address(0));
    require(amount <= _balances[account]);
    _totalSupply = _totalSupply - amount;
    _balances[account] = _balances[account] - amount;
    emit Transfer(account, address(0), amount);
}

_burnFrom

The _burnFrom function removes a number of tokens from the given account, which is also deducted from the caller's allowance.

function _burnFrom(address account, uint256 amount) internal {
    require(amount <= _allowed[account][msg.sender]);
    _allowances[account][msg.sender] = _allowances[account][msg.sender] - amount;

    _burn(account, amount);
}

The complete contract is given below.

/ SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.0;

import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";


contract ERC20 is Context, IERC20, IERC20Metadata {
    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

   
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

   
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    
    function decimals() public view virtual override returns (uint8) {
        return 2;
    }

   
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

   
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

  
    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }

    
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

   
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }

    
    function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

   
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

   
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

  
    function _transfer(address from, address to, uint256 amount) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
            // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
            // decrementing then incrementing.
            _balances[to] += amount;
        }

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }

  
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        unchecked {
            // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
            _balances[account] += amount;
        }
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

  
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
            // Overflow not possible: amount <= accountBalance <= totalSupply.
            _totalSupply -= amount;
        }

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

  
    function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }

  
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}

   
    function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}


Clone this wiki locally