ERC20 Token Implementation With EIP20 Standard: A Deep Dive into the Technical Details.

Prerequisites

  • Basic Ethereum knowledge

  • Basic Solidity knowledge

  • Knowledge of EIP20

What is an ERC20 token?

ERC20 stands for Ethereum Request For Comment 20. It is a technical standard developers can leverage to create fungible tokens. All ERC20 tokens are equal and the same, i.e no tokens have special rights or behavior associated with them. A fungible token is exchangeable with another token. Also, an ERC20 token smart contract keeps track of fungible tokens. It follows the EIP20 standard proposed by Fabian Vogelsteller in November 2015. In a nutshell, an ERC20 token smart contract must implement the following methods and events;

// Methods
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
event Transfer(address indexed _from, address indexed _to, uint256 _value)
event Approval(address indexed _owner, address indexed _spender, uint256 _value)

Although, some of these methods are optional, including them will increase the usability of the token.

Meanwhile, tokens frequently represent assets and rights that exist outside the blockchain ecosystem, but in the context of ERC-20 compliance, a "token" specifically refers to a blockchain-based digital representation that adheres to the standards of the Ethereum community for smart contract compatibility. "Token" and "Cryptocurrency" are often used interchangeably, but all cryptocurrencies are tokens, and not all tokens are cryptocurrencies.

Now, let’s delve deep into writing solidity smart contract to create our ERC20 token, thereby following the EIP20 implementation standard.

First, let’s declare our state variables

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

contract ERC20 {
    //the state variables
    string tokenName;
    string tokenSymbol;
    uint256 totalSupply;
    address owner;
}

Here, we have ERC20 contract declared, with state variables to store the token’s name, symbol, total supply, and the address of the token’s owner.

The next thing is to map from the address of a user to the balance of this token they are holding, which helps us to track the balance of the token on the user’s address.

    //making an amount trackable by address
    mapping (address user => uint256) balances;

Now, let’s track the amount allowed by a user for a spender to spend on their behalf by mapping from the user’s address to the token address and to the amount allowed.

  //making an amount trackable by address A  and address A traced by address B
    //key => (key => value)
    mapping (address => mapping (address => uint256)) allow;

The next thing is to declare our constructor that will be called during the deployment. This constructor takes two parameters that will take their values before deployment. It sets the values for our state variables such as tokenName, tokenSymbol, owner (which will be set to the deployer of the contract). We also called the function that will mint 1,000,000 tokens to the owner.

constructor(string memory _name, string memory _symbol) {
        tokenName = _name;
        tokenSymbol = _symbol;
        owner = msg.sender;
        //mint method
        mint(1_000_000, owner);
    }

Remember the two events in the ERC20 token standard? The Transfer event will be emitted whenever a transfer function is being fired and successful, while the Approval event will be emitted whenever the approve function is being fired and successful. Let’s declare them;

 // event for logging
    event Transfer(address indexed sender, address indexed receiver, uint256 amount);

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

Let’s declare all the six getter functions in the standard. They are there to help us read from the blockchain;

    function getTokenName() external view returns (string memory){
        return tokenName;
    }

    function getSymbol() external view returns (string memory){
        return tokenSymbol;
    }

    function getTotalSupply() external view returns (uint256){
        return totalSupply;
    }

    function decimal() external pure returns(uint8){
        return 18;
    }

    //balance check
    function balanceOf(address _address) external view returns (uint256){
        return balances[_address];
    }

     function allowance(address _owner, address _delegate) external view returns (uint) {
        return allow[_owner][_delegate];
    }

Below is the transfer function implementation logic. This function takes two parameters: the tokens receiver and the amount of tokens to be transferred. In the function, the first line is for a sanity check to make sure that, we are not transferring to address zero. The second line also is for sanity check to make sure that, the sender has enough of the amount of token they want to transfer in their balances. Then, if two sanity checks are passed, in the next line we want want to remove the amount from the sender’s balance and the next line help us to add the amount sent to the receiver’s balance. The Transfer event will then be emitted next.

function transfer(address _reciever, uint256 _amountOfToken) external  {
        require(_reciever != address(0), "Transfer to the zero address is not allowed");

        require(_amountOfToken <= balances[msg.sender], "You can't take more than what is avaliable");

        balances[msg.sender] -=  _amountOfToken;

        balances[_reciever] = balances[_reciever] + _amountOfToken;

        emit Transfer(msg.sender, _reciever, _amountOfToken);
    }

The approve function works hand in hand with transferFrom and the allowance functions. It allows the sender to approve a certain amount of token for the spender, who will spend the token on their behalf, before calling the transferFrom function itself. So allowance function helps to read the current amount approved by the sender for the spender. This function takes two parameters: delegate(spender) address and the amount to be approved. The first line of code in the function below checks if the user has enough funds to be approved. The second line keeps track of the approved amount and sets it as the allowance amount by the owner of the funds, while the last line fires the Approval event if everything went well.

function approve(address _delegate, uint256 _amountOfToken) external {
        require(balances[msg.sender] > _amountOfToken, "Balance is not enough");

        allow[msg.sender][_delegate] = _amountOfToken;

        emit Approval(msg.sender, _delegate, _amountOfToken);
    }

Below is the implementation of transferFrom, which takes three parameters; owner’s address, spender address(buyer), and the amount to be spent. The logic makes sure the buyer and sellers are not address zero. It also checks if the balance of the owner of the fund is enough, and if it is equivalent to the amount they have approved. After the sanity checks, the amount is deducted from the owner’s token total balance and the current allowance. The amount is then added to the spender’s token balance and the transfer event is fired if everything went well.

function transferFrom(address _owner, address _buyer, uint256 _amountOfToken) external {
        //sanity check
        require(_owner != address(0), "Address is not allowed");
        require(_buyer != address(0), "Address is not allowed");

        require(_amountOfToken <= balances[_owner]);
        require(_amountOfToken <= allow[_owner][msg.sender]);

        balances[_owner] = balances[_owner] - _amountOfToken;

        allow[_owner][msg.sender] -= _amountOfToken;

        balances[_buyer] = balances[_buyer] + _amountOfToken;

        emit Transfer(_owner, _buyer, _amountOfToken);
    }

Remember we called mint function in the constructor at the beginning, right? This function used internal visibility specifier, because we only want to call it within the contract. It takes two parameters; the amount of token we want to mint and the address we want to mint to. Then, in the function, we set the amount as the value for the total supply, and added it to the address we are minting to, and fired the Transfer event. By defualt, the token we are minting is coming from address zero. And boom!!! we have an erc20 token contract ready to be deployed.

//method called in the constructor
    function mint(uint256 _amount, address _addr) internal {
        uint256 actualSupply = _amount * (10**18);
        balances[_addr] = balances[_addr] + actualSupply;

        totalSupply = totalSupply + actualSupply;

        emit Transfer(address(0), _addr, actualSupply);
    }

If you want a full source code, check https://github.com/DevBigEazi/ERC_20-Token/blob/main/contracts/ERC20_token.sol

Conclusion

When ERC-20 tokens are sent to a smart contract that is not designed to handle ERC-20 tokens, those tokens can be permanently lost. This happens because the receiving contract does not have the functionality to recognize or respond to the incoming tokens, and there’s no mechanism in the ERC-20 standard to notify the receiving contract about the incoming tokens. The main ways this issue takes form is:

  • When a user sends tokens to a contract address using these functions (transfer and transferFrom), the tokens are transferred regardless of whether the receiving contract is designed to handle them.

  • When the receiving contract does not receive a notification or callback that tokens have been sent to it.

  • If the receiving contract lacks a mechanism to handle tokens (e.g., a fallback function or a dedicated function to manage token reception), the tokens are effectively stuck in the contract’s address

  • The ERC-20 standard does not include a mandatory function for receiving contracts to implement, leading to a situation where many contracts are unable to manage incoming tokens properly

Some alternative standards have come out of this issue such as ERC-223.

Refrences

https://www.investopedia.com/news/what-erc20-and-what-does-it-mean-ethereum/

https://ethereum.org/en/developers/docs/standards/tokens/erc-20/

https://www.coinbase.com/learn/crypto-glossary/what-is-erc-20

0
Subscribe to my newsletter

Read articles from Isiaq A. Tajudeen directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Isiaq A. Tajudeen
Isiaq A. Tajudeen