Introduction To ERC Token Standards — Part 1

Immunefi
Immunefi
Published in
8 min readFeb 23, 2022
Hong Kong Lights / yuumeiart.com

Introduction

This article aims to help you understand some of the improvements made to the ERC token standards, as well as the problems the Ethereum community has been working on solving. We’ll start from the standard ERC-20 to the ERC-1363 payable token standard and discuss the problems and improvements of popular token proposals made by the community.

Before we jump into the various ERC standards, let’s first understand the ERC or EIP in the Ethereum space and their contribution to the ecosystem.

EIP/ ERC

EIP (Ethereum Improvement Proposal) are proposals drafted by members of the community to improve the Ethereum protocol. An EIP is similar to many research papers put forward by Ethereum community members proposing improvements to cryptocurrency and smart contracts. Each proposal contains an abstract, motivation (the underlying reasoning), and technical specifications. In addition, it should also include a rationale and information relating to backward compatibility, security considerations, and test cases, etc. A team of editors reviews these EIPs and decides which to move forward for a potential upgrade.

There are four types of standard EIPS:

  1. Core: mentions or proposes changes to the EVM (Ethereum Virtual Machine)
  2. Networking: improvements around transport protocols that are responsible for communication among Ethereum nodes (RLPx)
  3. Interface: improvements around client API/RPC specifications and standards
  4. Application: improvements around application or token level standards, e.g. ERC20

For this article, we will focus only on the application-level ERC standards.

ERC stands for Ethereum Request for Comment, and EIP20 or ERC20 is an official protocol for proposing improvements to the Ethereum (ETH) network, You can view all the token standard proposals made by the community here: https://eips.ethereum.org/erc.

ERC-20

The ERC-20 standard is still widely used and plays a significant role in the Ethereum ecosystems. It’s the mother of all ERC standards. Tokens are fungible tokens, which means each of the tokens has the exact same value — for example, coins, loyalty points, in-game points, etc. ERC-20 tokens are stored and sent using Ethereum addresses and transactions, and use gas to cover transaction fees in the native asset, i.e., ETH.

The following is the ERC20 token standard interface:

  1. name(): returns the name of the token. E.g. Tether, USD Coin
  2. symbol(): returns the symbol of the token. E.g. USDT, USDC
  3. decimals(): returns the smallest unit representation of the token. E.g. 18 decimals

The unit is used by smart contracts for accounting, as smart contracts work on integers, meaning that there can’t be any decimals. This unit is displayed to users (in any UI, from wallets to exchanges or any dApp). This is comparable to Ether, which uses 18 decimals for display. When showing 1080250000000000000000 for 18 decimals, it is much more user friendly to display it as 1'080.25 instead. Different tokens can opt for any decimals they want. USDC has 6 decimals, and USDT has 18 decimals. But by default, most ERC20 tokens choose 18 decimals.

4. totalSupply(): returns the total number of tokens in circulation

5. balanceOf(address): returns the number of tokens held by the address

6. approve(address, amount): this allows you to give allowance to the user within the specified limit. Alice can call approve(bob,10). This allows Bob to have a 10 tokens allowance from Alice’s account; hence, Bob can take 10 tokens from Alice any time by calling transferFrom(alice, bob,10)

7. transferFrom(from, to, amount): this allows users to move funds on behalf of the sender; if the sender gave allowance to the recipient with a specified amount, then the recipient can move the allowed amount on behalf of the sender

8. allowance(sender, recipient): returns the amount of approved amount to the recipient address by the sender

The core functionality of ERC20 is transfer()

It allows the users to move funds from one account to another. The function transfer() subtracts the amount from the sender’s balance and adds the amount to the recipient’s balance. Example: if Alice calls transfer(bob,50), then the following before-after accounting happens at token for balances.

Before:

_balances[Alice] = 100_balances[Bob] = 0

After:

_balances[Alice] = 50_balances[Bob] = 50

Major Drawbacks of ERC20 Standards

  1. Impossibility of handling incoming token transactions.

ERC20 has only functionality for moving and accounting for funds. There is no way to handle incoming token transactions and no way to reject any non-supported tokens. Tokens are simply credited to the receiver contract. Therefore, there is no way for the receiver to know about the credit and act upon the credited amount.

2. The transfer process is inefficient.

To transfer the tokens from account A to account B, users can call the transfer() function of the ERC20 token. For example, Alice can call transfer(bob.address,10). This method is suitable and safe for transferring to EOA (Externally Owned Accounts) only.

However, if you want to transfer the tokens to the contract, you need to ensure that the receiver contract can handle the incoming transfers–that it is compatible with the ERC20 standard interface. An accidental token transfer to a non-ERC20 contract will result in a loss of tokens. If you transfer the tokens directly to a non-ERC20 contract, then the transfer will be successful, but the tokens are stuck forever in the receiver contract, since it doesn’t have the functionality to move the tokens from the contract.

To send the funds to a smart contract, Alice needs to use the approve and transferFrom combination.

a. First, the user needs to set allowance for the receiverContract with an amount: approve(receiverContract,100)

b. Then, the user has to make a call from receiverContract to move user funds from the ERC20 token contract to the receiver contract using transferFrom(Alice,receiverContract,100)

c. This pattern is also gas-inefficient, since it requires two separate transactions to move the funds from the user to the receiver contract.

ERC-223

ERC223 was introduced to fix major drawbacks in the ERC20 standard. It offers a solution to save token losses due to accidental transfers and gives the ability to receivers to accept or decline tokens arriving at their contract address. It no longer follows the (approve + transferFrom) ERC20 multistep standard to transfer the tokens to the contract. It is also a gas-efficient solution, as it requires a single transaction to transfer the tokens to the receiver contract.

ERC223 introduces a callback function for a receiving contract to handle the incoming tokens via the tokenReceived function. Users can now define a custom logic to accept or decline tokens arriving at their contact address using the tokenReceived function. If the recipient address doesn’t have a tokenReceived function, then the transaction will revert, preventing the accidental transfers to non-supported standard contracts which have plagued ERC20.

Let’s look at how the transfer() function looks for ERC223.

Now, the transfer() function has additional functionality which takes new optional argument data, which can be used to make a call to the recipient contract with the data supplied by the caller. The recipient contract can then use the supplied data to do something. This behavior also introduces the concept of transfer + call. This transfers the tokens and also instructs for something to be done on the recipient contract.

If the recipient address is a contract, then the ERC223 token makes a call to the recipient contract on tokenReceived function.

In the above code example, contracts can define their own logic to accept the incoming token transfer or to reject or many more.

The major drawback of ERC223 is that it overrides the design of transfer(address,uint256,bytes)function because it changes the behavior of standard ERC20’s transfer(address,uint256). It’s not backward compatible with existing ERC20 compatible contracts, since it always has a requirement of tokenReceived() function on the receiver contract; otherwise, the transfer will simply revert.

ERC-677

ERC677 aims to provide useful functionality without colliding with the ERC20 standard. This standard adds a new function transferAndCall() to the existing ERC20 standard, unlike the ERC223, which overrides the existing transfer() function.

transferAndCall() can be called to transfer tokens to a contract and then call the recipient contract with the additional data supplied by the sender. ERC677 tokens can be stored in any ERC20-compatible wallet. Receiving contracts could have the function onTokenTransfer(address,uint256,bytes) to write custom logic for the incoming call.

The major advantage ERC-677 has is that even if the recipient contract doesn’t have the onTokenTransfer() function, we can still use the regular ERC20 function transfer(address,uint256) to transfer the tokens. Hence the requirement of the dependent function on the recipient contract is optional. This makes it compatible with existing ERC20 tokens in space as we can use regular transfer() to transfer the tokens.

One of the famous tokens which uses ERC677 is ChainLink (LINK) https://etherscan.io/address/0x514910771af9ca656af840dff83e8264ecf986ca#code

To do an API call or Chainlink VRF (Verifiable Random Function) request requires a function call on a smart contract. To do so, you also require a LINK token. With the regular ERC20 standard, you have to do it in multiple steps, e.g. (approve + call) on the contract. But using ERC677, you can do both in a single transaction transferAndCall(to,value,data). This first transfers the tokens to the receiver, then checks if the recipient is the contract, then makes a call on the recipient onTokenTransfer() along with the data.

The receiver contract checks if the incoming token call (msg.sender) is coming from the LINK address. Then it decodes the data supplied to the transfer and emits the RandomnessRequest event.

ERC-1363

ERC1363 does a similar thing to ERC677 but with some great extensions. In ERC677, we just had a transferAndCall() function, but EIP-1363 adds two more functions as extensions to the ERC677 with ERC20 standard compatibility.

  • transferFromAndCall(..) : Similarly to the transferAndCall(), the Receiver contract can now transfer the tokens from the spender to the receiver within the specified allowance limit and do something with a call.
  • approveAndCall(..) : Sets the allowance amount to the spender and do something with a call.

Using approveAndCall(), now the token can make a callback to the recipient contract after approving the tokens to the recipient to instruct them to do something. By doing so, contracts can accept ERC-20 payments to create a token payable crowdsale, selling services for tokens, paying invoices, making subscriptions, or use them for a specific utility and many other purposes.

Ending Note

In this piece, we introduced the new era in ERC tokens, where tokens now do a callback to a function on the recipient address, allowing further possibilities of handling the calls. But this also involves a risk if the contracts aren’t correctly following security standards to handle the tokens, resulting in devastating vulnerabilities like reentrancy.

The next article in this series will cover the most common vulnerabilities with ERC standards and the factors to consider when integrating the tokens into the contracts.

--

--

Immunefi
Immunefi

Immunefi is the premier bug bounty platform for smart contracts, where hackers review code, disclose vulnerabilities, get paid, and make crypto safer.