DEV Community

Cover image for 0 to 100: ERC20 token
MOYED
MOYED

Posted on

0 to 100: ERC20 token

Sources

ERC-20 Contract Walk-Through | ethereum.org

openzeppelin-contracts/contracts/token/ERC20 at v3.0.0 · OpenZeppelin/openzeppelin-contracts

ERC20 | Solidity by Example | 0.8.10

openzeppelin-contracts/ERC20.sol at master · OpenZeppelin/openzeppelin-contracts


Overview

Contract that follows ERC20 standard is called ERC20 token.

  • transfer tokens
  • allow others to transfer tokens on behalf of token holder

IERC20.sol

Interface of ERC20 standard

  • totalSupply

    Returns amount of tokens in existence.

    function totalSupply() external view returns (uint256);
    
  • balanceOf

    Returns balance of token owned by specific account.

    function balanceOf(address account) external view returns (uint256);
    
  • transfer

    Move given amount of tokens from caller’s account to recipient.

    function transfer(address recipient, uint256 amount) external returns (bool);
    
  • approve

    Give spender permission to spend my(caller)’s token for given amount . Returns boolean indicates whether it succeed or not.

    function approve(address spender, uint256 amount) external returns (bool);
    
  • allowance

    Returns the amount of token that spender is allowed to used be half of owner .

    function allowance(address owner, address spender) external view returns (uint256);
    
  • transferFrom

    Moves amount of token from sender to recipient (allowance mechanism). amount is deducted from caller’s allowance.

    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    
  • Transfer(event)

    Emitted from transferFrom or transfer function.

    event Transfer(address indexed from, address indexed to, uint256 value);
    
  • Approval

    Emitted from approve or transferFrom function. value is new allowance from owner to spender .

    event Approval(address indexed owner, address indexed spender, uint256 value);
    

ERC20.sol

Implementation of IERC20 interface.

  • private(internal) function, state variables are conventionally written as _<something> .
  • We use internal functions to minimize the number of places where state changes happen because changing state is potential security risk.

Imported files

  • Context.sol

    Information about current execution context. _msg.Sender function returns msg.sender and _msgData returns [msg.data](http://msg.data) .

    OpenGSN allows etherless users to use the blockchain. That’s why we use custom _msgSender function instead of msg.sender .

  • IERC20.sol

  • SafeMath.sol

    try_ type functions revert transaction when operation overflows.

  • Address.sol

    Collection of functions associated to address type. (Ex: sendValue for safe alternative to transfer , functionCall to safe alternative to call ...)

Constructor

name , symbol are initialized and decimals have default initialized value of 18.

constructor (string memory name, string memory symbol) public {
        _name = name;
        _symbol = symbol;
        _decimals = 18;
    }
Enter fullscreen mode Exit fullscreen mode
  • Function arguments are always in memory because they only need to exist for execution of function.
  • About decimals

    if decimals equals 2, balance is 505, display is 5.05

Internal methods

  • _transfer

    Check sender and recipient are not zero address. Subtract amount from _balance[sender] and add amount to _balance[recipient . Emit Transfer event.

    function _transfer(address sender, address recipient, uint256 amount) internal virtual {
            require(sender != address(0), "ERC20: transfer from the zero address");
            require(recipient != address(0), "ERC20: transfer to the zero address");
    
            _beforeTokenTransfer(sender, recipient, amount);
    
            _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
            _balances[recipient] = _balances[recipient].add(amount);
            emit Transfer(sender, recipient, amount);
        }
    
  • _mint

    Check account is not zero address. Add amount to _totalSupply and _balances[account] . Emit Transfer event which sender is address(0) .

    function _mint(address account, uint256 amount) internal virtual {
            require(account != address(0), "ERC20: mint to the zero address");
    
            _beforeTokenTransfer(address(0), account, amount);
    
            _totalSupply = _totalSupply.add(amount);
            _balances[account] = _balances[account].add(amount);
            emit Transfer(address(0), account, amount);
        }
    
  • _burn

    Check account is not zero address. account is account that the token is burned from. Subtract amount to _totalSupply and _balances[account] . Emit Transfer event which recipient is address(0) . Perfect opposite of function _mint .

    function _burn(address account, uint256 amount) internal virtual {
            require(account != address(0), "ERC20: burn from the zero address");
    
            _beforeTokenTransfer(account, address(0), amount);
    
            _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
            _totalSupply = _totalSupply.sub(amount);
            emit Transfer(account, address(0), amount);
        }
    
  • _approve

    Sets allowance of spender over owner ’s token. Check both are not zero address. Assign amount to _allowance[owner][spender] . Emit Approval event.

    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);
        }
    
  • _setupDecimals

    Assign new decimal value.

    function _setupDecimals(uint8 decimals_) internal {
            _decimals = decimals_;
        }
    

increaseAllowance & decreaseAllowance

Alternative to approve for get away with issue of approve, which changing allowance with approve can allow spender to use both old and new allowance.

  • increaseAllowance

    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
            _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
            return true;
        }
    
  • decreaseAllowance

    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
            _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
            return true;
        }
    

Making own ERC20 token + Tokenswap Contract

ERC20 | Solidity by Example | 0.8.10

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

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/contracts/token/ERC20/IERC20.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/contracts/token/ERC20/ERC20.sol";

/*
How to swap tokens

1. Alice has 100 tokens from AliceCoin, which is a ERC20 token.
2. Bob has 100 tokens from BobCoin, which is also a ERC20 token.
3. Alice and Bob wants to trade 10 AliceCoin for 20 BobCoin.
4. Alice or Bob deploys TokenSwap
5. Alice appproves TokenSwap to withdraw 10 tokens from AliceCoin
6. Bob appproves TokenSwap to withdraw 20 tokens from BobCoin
7. Alice or Bob calls TokenSwap.swap()
8. Alice and Bob traded tokens successfully.
*/
contract moyed is ERC20 {
    constructor() ERC20("MOYED", "MYD") {
        _mint(msg.sender, 100 * 10**18);
    }
}

contract golden is ERC20 {
    constructor() ERC20("GOLDEN", "GLD") {
        _mint(msg.sender, 100 * 10**18);
    }
}

contract TokenSwap {
    IERC20 public token1;
    address public owner1;
    uint public amount1;
    IERC20 public token2;
    address public owner2;
    uint public amount2;

    constructor(
        address _token1,
        address _owner1,
        uint _amount1,
        address _token2,
        address _owner2,
        uint _amount2
    ) {
        token1 = IERC20(_token1);
        owner1 = _owner1;
        amount1 = _amount1;
        token2 = IERC20(_token2);
        owner2 = _owner2;
        amount2 = _amount2;
    }

    function swap() public {
        require(msg.sender == owner1 || msg.sender == owner2, "Not authorized");
        require(
            token1.allowance(owner1, address(this)) >= amount1,
            "Token 1 allowance too low"
        );
        require(
            token2.allowance(owner2, address(this)) >= amount2,
            "Token 2 allowance too low"
        );

        _safeTransferFrom(token1, owner1, owner2, amount1);
        _safeTransferFrom(token2, owner2, owner1, amount2);
    }

    function _safeTransferFrom(
        IERC20 token,
        address sender,
        address recipient,
        uint amount
    ) private {
        bool sent = token.transferFrom(sender, recipient, amount);
        require(sent, "Token transfer failed");
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)