A Developer’s Overview of The ERC-20 Token Standard
What is ERC-20 all about? How you can build an ERC-20 contract
Photo by Nenad Novaković on Unsplash
At the initial stage in the Ethereum ecosystem, the developer teams of each DeFi project created their smart contracts according to their peculiar dispositions.
This caused disparity among several smart contracts that were running on the network. Therefore, the Ethereum-based contracts at that time could not interact with one another.
This birthed the famous ERC-20 standard, which we shall examine in this piece.
What does ERC-20 mean?
ERC-20 means Ethereum Request for Comments 20, which is an approved template or framework for developers to build transferrable and identical crypto tokens on the Ethereum protocol.
In June 2015, Vitalik Buterin and Fabian Vogelstellar submitted a proposal to the Ethereum community. The title of the proposal was Standard Contract APIs.
The main argument in the proposal was on the excessive variations in the functionalities of the tokens of Ethereum-based contracts. Therefore, they advocated for a common pedestal for developers to build DeFi tokens.
After several communal deliberations and procedures, the Ethereum community accepted the proposal in November as EIP-20.
It is noteworthy that once ERCs are finalized, they are referred to as EIPs. But for some unexplainable reasons, the Ethereum community still refers to this framework as ERC-20 to date.
What are the examples of ERC-20 tokens?
Examples of ERC-20 tokens are DAI, MATIC, AVAX, USDT, AAVE, SAND, CRO, NEAR, ENJ, and USDC to mention a few.
According to Etherscan, which is the sole block explorer in the Ethereum ecosystem, there are almost 600,500 contracts that are leveraging the ERC-20 standard.
It is safe to say that the contracts of 93% of the entire crypto tokens—note, not non-fungible tokens—in the world are based on ERC-20.
What are the major distinguishing factors of ERC-20?
The distinguishing factors of ERC-20 tokens are that they are exchangeable, transferrable, and ownable, and the entirety of their supply can be programmed.
We can break these factors down into 6 compulsory functions and 2 events.
function totalSupply() external view returns (uint);
function balanceOf(address account) external view returns (uint);
function transfer(address recipient, uint amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);
event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed owner, address indexed spender, uint value);
These functions and events are both readable and self-explanatory. First, an ERC-20 token must have a total supply - it must not be unlimited.
The second function furnishes us with the balance of an owner.
Moving on, transfer
and transferFrom
are similar, but they are different; the owner of the contract calls the transfer
function, but all other users can call the transferFrom
function as a form of withdrawal.
The approve function allows the users to allow the contract to release or spend some tokens on their behalf. As you can see, it returns a boolean value; meaning it can either be successful or otherwise.
Having discussed the salient functions, let's discuss the two events:
Recall that we have to emit events whenever we want to communicate with the transaction logs. Now, you should discharge the Approval event whenever you call the approve function.
In addition, the Transfer event should go pari-passu with the transfer function.
At this juncture, we should point out that there are about three other optional functions; decimals, name, and symbol. But we can simply define them in the state, and most developers prefer this method.
Do ERC-20 tokens have any downsides at all?
Well, it is not the case that ERC-20 tokens have some downsides per se. The intention of the developers or projects will determine if it is fit for purpose.
Nonetheless, here are two main downsides of the standard. The first one is dependence on Ethereum. The future is multichain, as most developers would want to believe.
However, if the Ethereum ecosystem chains all the crypto tokens together, it poses a threat that all other tokens would have to bear with the ills of Ethereum such as high gas fees and slow throughput.
The second downside is Fungibility. Although fungibility is the core of this standard, it can be a downside because not all tokens should be fungible.
Thus, if the community or developers intend to build an NFT, the ERC-20 standard is a misfit.
How to Build an ERC-20 Smart Contract
So far, we have discussed the theoretical underpinning of ERC-20. Here is a typical ERC-20 contract.
// SPDX-License-Identifier : MIT
pragma solidity ^0.8.16;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/token/ERC20/IERC20.sol";
contract Code is IERC20{
string public name = "Degen Token";
string public symbol = "DGT";
uint public decimals = 3;
uint public totalSupply = 1000;
mapping(address => uint) public balanceOf;
mapping (address => mapping (address => uint)) public allow;
function minting(uint amount) external {
balanceOf[msg.sender] += amount;
totalSupply += amount;
emit Transfer(address(0), msg.sender, amount);
}
function burning(uint amount) external {
totalSupply -= amount;
balanceOf[msg.sender] -= amount;
emit Transfer(msg.sender, address(0), amount);
}
function transfer(address recipient, uint amount) external returns (bool){
balanceOf[msg.sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(msg.sender, recipient, amount);
return true;
}
// include this function so the compiler doesn't throw
// the "mark as abstract" error
function allowance(address owner, address spender) external virtual view returns (uint){
}
function approve(address spender, uint amount) external returns (bool){
allow[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool){
allow[sender][msg.sender] -= amount;
balanceOf[sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(sender, recipient, amount);
return true;
}
}
Now, let’s explain the ratio behind the codes one after the other:
After declaring the version of our compiler, the first thing to do is to import the ERC-20 code base from the Open Zeppelin website. Another way to do this is to use the interface.
pragma solidity ^0.8.16;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/token/ERC20/IERC20.sol";
We created our contract and named it Code. If you are familiar with crypto, you will know tokens always have a name and symbol such as how the Avalanche token is AVAX for such. If you are also observant, you will also know that crypto tokens always have 18 decimals.
In the instant case, we named the tokens of our Code contract “Degen Token,” with “DGT” as the symbol, and we wanted it to appear in 3 decimals. Then we made the total supply of the Degen Tokens to 1,000.
string public name = "Degen Token";
string public symbol = "DGT";
uint public decimals = 3;
uint public totalSupply = 1000;
At this juncture, we will have to map the address of our users to track the balance. Secondly, we will need to nest a map that will enable the sender to authorize the owner of the contract to spend on their behalf.
mapping(address => uint) public balanceOf;
mapping (address => mapping (address => uint)) public allow;
Although we set the total supply to 1,000 at the start, we need to be realistic and prepare for the future accordingly. Hence, the reason we introduced the minting and burning functions.
The minting function will enable the owner to create new Degen Tokens. On the other hand, the owner can destroy some number of tokens by calling the burn function.
function minting(uint amount) external {
balanceOf[msg.sender] += amount;
totalSupply += amount;
emit Transfer(address(0), msg.sender, amount);
}
function burning(uint amount) external {
totalSupply -= amount;
balanceOf[msg.sender] -= amount;
emit Transfer(msg.sender, address(0), amount);
The transfer transaction makes it possible for the owner of the contract to send whoever are the receivers. Meanwhile, note the boolean nature of the function.
function transfer(address recipient, uint amount) external returns (bool){
balanceOf[msg.sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(msg.sender, recipient, amount);
return true;
}
The next function is the approve function with which the spender is allowed to spend the funds of the owner.
function approve(address spender, uint amount) external returns (bool){
allow[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
Now, there is the transferFrom function which enables some recognized addresses in the contract to send funds to others. We will allow the spender to spend the funds of the owner.
Subsequently, we will decrement the amount from their addresses accordingly and credit the recipient, emit the Transfer event and return true.
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool){
allow[sender][msg.sender] -= amount;
balanceOf[sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(sender, recipient, amount);
return true;
}
If you try to compile the contract this way, you will notice an error that you will have to make it an abstract contract, which is unfit for our purpose.
The reason for this error is that you didn’t implement all the functions in the code base you are interfacing. Click on the error, and Remix will inform you of the specific function you didn’t add.
In the instant case, it will be because of the allowance function. Therefore, we shall add it herein:
function allowance(address owner, address spender) external virtual view returns (uint){
}
You can now compile and deploy. There you have a typical ERC-20 contract with an ERC-20 token!
Wrapping Up - How ERC-20 Opened the Floor for more Innovations
The ERC-20 standard paved way for greater innovations. After its proposal was accepted, we had the ERC-721 which introduced the standardization of NFT contracts.
Several times after that, there have been various technical proposals on rental NFTs, proto-danksharding, and so on. But then, it is a truism that ERC-20 was the cornerstone of standards in the Ethereum ecosystem.