Understanding ERC-20 token contracts
Earlier this week the ERC-20 token interface became a formal improvement proposal, freezing the definition. This article takes a look at tokens and explains the features and functions of ERC-20 to provide an understanding of what token contracts are and how developers can work with them.
ERC-20 came about as an attempt to provide a common set of features and interfaces for token contracts in Ethereum, and has proved to be very successful. ERC-20 has many benefits, including allowing wallets to provide token balances for hundreds of different tokens and creating a means for exchanges to list more tokens by providing nothing more than the address of the token’s contract. The benefits of creating a token as ERC-20 compliant is such that very few token contracts today are created any other way.
What is a token contract?
There remains a lot of confusion around what token contracts really are. Essentially, a token contract is a smart contract that contains a map of account addresses and their balances. The balance represents a value that is defined by the contract creator: one token contract might use balances to represent physical objects, another monetary value, and a third the holder’s reputation. The unit of this balance is commonly called a token.
When tokens are transferred from one account to another the token contract updates the balance of the two accounts. For example, a transfer of 10 tokens from 0x2299…3ab7 to 0x1f59…3492 would result in the balances being updated as shown below:
It is possible to change the total supply of a token in two ways, if the token contract allows. The total supply of tokens can be increased by minting new tokens. For example, minting 100 tokens to 0x4ba5…ae22 would result in the balances being updated as shown below:
The total supply of tokens can be decreased by burning existing tokens. For example, 0x4919…413d burning 50 tokens would result in the balances being updated as shown below:
An alternative method of burning tokens is to send the tokens to an address to which the private key is not known, commonly the 0 address. This has the same effect as burning tokens in terms of making the tokens unavailable to spend, but does not decrease the total number of tokens. For example, 0x93f1…1b09 burning 50 tokens in this fashion would result in the balances being updated as shown below:
Simple token contracts hold the above information in a mapping of address to balance. When more complex scenarios come in to play such as providing dividends then alternative or additional structures will often be more powerful. Regardless of the implementation details, however, the view of token balances to the outside world should always look like the diagrams shown above.
The definition of an ERC-20 token contract
An ERC-20 token contract is defined by the contract’s address and the total supply of tokens available to it, but has a number of optional items that are usually provided as well to provide more detail to users. These are the token’s name, its symbol, and the number of decimals. Each of these is covered in detail below.
Before going in to the details, it is important to understand that there is no central registry for token contracts so the uniqueness of a particular name or symbol is not guaranteed. Once you have created a token contract you should ask for it to be added to common sites such as Etherscan, MyEtherWallet and CoinMarketCap, although be sure to follow the instructions at the links provided for your best chance of the submission being accepted.
name of the token contract is the long name by which the token contract should be known, for example “My token”. There are no restrictions on the length of this name but long names are likely to be truncated in some wallet applications so it’s best to keep the name short.
symbol of the token contract is the symbol by which the token contract should be known, for example “MYT”. It is broadly equivalent to a stock ticker, and although it has no restriction on its size it is usually 3 or 4 characters in length.
decimals are a common source of confusion, but are quite easy to understand with suitable explanation.
decimals refers to how divisible a token can be, from 0 (not at all divisible) to 18 (pretty much continuous) and even higher if required. Technically speaking, the
decimals value is the number of digits that come after the decimal place when displaying token values on-screen. The reason that
decimals exists is that Ethereum does not deal with decimal numbers, representing all numeric values as integers. Consider the following two examples:
The first example uses LicenseToken, a token contract that represents software license assignment for a given software product; users holding a LicenseToken have access to the software. It makes no sense for a user to hold a fraction of a license, so the token creator sets
0. Some holders of LicenseToken are represented graphically below.
Here it can be seen that there is a total of 100 licenses, mainly held by one account. As users purchase a license a single token will be transferred from the holding account to the purchaser. The license validator can check to see if a particular account holds a license token and act accordingly.
The second example uses GoldToken, a token contract that represents ownership of physical gold. The token creator wants the unit of to represent 1 kilogram of gold, but also wants to allow users to hold and trade amounts of gold down to the gram level (but no lower). As Ethereum does not support decimal numbers a token must represent 1g of gold, and to represent 1,000g as a single 1Kg unit to the external world the
decimals are set to
3 (as there are 10³ grams in the kilogram of gold that the token creator wishes to be displayed as 1 token). Some holders of GoldToken are represented graphically below.
Here it can be seen that there is a total of 50Kg of gold represented (1g per token * 50000 tokens). However, as
decimals is set to
3 the view to the user will be as follows:
It can be seen that setting
decimals to 3 literally means that when displaying GoldToken balances there should be 3 digits of the balance after the decimal point.
decimals is often called a humanising factor because it allows token contracts to define how they would like balances to be displayed to users. GoldToken doesn’t deal with decimals internally, and never uses
decimals in its own calculations as everything is in grams, but allows users to work with a common unit of gold (the Kg) rather than that used in the contract (g).
The idea of divisibility as shown above in GoldToken allows token contracts to represent very fine-grained decimal values, and tokens are commonly built with
decimals set to
18 to give the token a near-continuous range of values.
In summary, when picking a suitable value for
decimals these rules should be followed:
- does the token contract represent an indivisible entity? Then set
- does the token contract represent an item with a fixed number of decimal places? Then set
decimalsto that number
- if neither of the above apply set
It is important to understand the impact of decimals on token creation. The number of tokens that should be created is equal to the whole number of tokens that are required multiplied by 10^
decimals. As can be seen with the GoldToken example, the token creator wanted to create tokens representing 50Kg of gold but to do so with 3 decimals they must mint 50,000 tokens (50 * 10³).
totalsupply is the final item that defines an ERC20 token contract, and as mentioned is the sole mandatory parameter. Although not explicitly stated in the ERC-20 specification the definition of
totalsupply is simple:
totalsupply equals the sum of all balances.
totalsupply has been seen in action in the above examples so needs no further discussion here.
Functions of an ERC-20 token contract
ERC-20 token contracts come with a number of functions to allow users to find out the balances of accounts as well as to transfer them from one account to another under varying conditions. These functions are described below.
balanceOf() function provides the number of tokens held by a given address. Note that anyone can query any address’ balance, as all data on the blockchain is public.
There are two ways to send tokens from one address to another. The
transfer() function transfers a number of tokens directly from the message sender to another address. Note that there are no checks made on the recipient address, so it is incumbent on the sender to ensure that the recipient is as intended.
transfer() is fine for sending tokens from one user to another it doesn’t work so well when tokens are being used to pay for a function in a smart contract. This is because at the time the smart contract runs it has no access to details of which addresses transferred funds where and so cannot assert that the user calling the contract has paid the required amount of funds to operate the contract.
Imagine that there is a contract
Doer deployed on the network.
Doer has a function
doSomething() that requires 10
Do tokens to operate. Joe wants to call
doSomething() and has 50
Do tokens in his account. How can Joe pay
Doer so that it can run
transferFrom() are two functions that allow the above scenario to work using a two-step process. In the first step a token holder gives another address (usually of a smart contract) approval to transfer up to a certain number of tokens, known as an allowance. The token holder uses
approve() to provide this information.
In the above example the second line shows that Joe with address 0x1f59…3492 has allowed
Doer at 0xd8f0…c028 to transfer up to 25 tokens from Joe’s account.
Once an allowance has been created the smart contract can take up to the allowed number of tokens from a user’s allowance as part of the contract’s operation. Continuing the example, Joe can now call
doSomething() can use
transferFrom() to take 10
Do tokens from Joe’s account and carry on its work. If Joe doesn’t have 10 tokens in their account, or their allowance is less than 10 tokens,
doSomething() will fail.
allowance() function provides the number of tokens allowed to be transferred from a given address by another given address. Note that anyone can query any address’ allowance, as all data on the blockchain is public. It is important to understand that allowances are “soft”, in that both individual and cumulative allowances can exceed an address’ balance. In the approval table shown above, for example, the holder 0x2299…3ab7 has approved a transfer of up to 500 tokens but their balance as seen previously is only 90 tokens. Any contract using
allowance() must additionally consider the balance of the holder when calculating the number of tokens available to it.
Events of an ERC-20 token contract
ERC-20 defines two events that must be triggered when the contract takes the relevant action. The first event is
Transfer() which emits details of the movement of tokens from one address to another. The second event is
Approval() which emits details of approvals of tokens from one address to another. These can be used to keep track of balance and allowance changes for addresses without needing to poll the blockchain.
Minting tokens emits a
Transfer() event with the 0 address as the source.
There is no event emitted when tokens are burned. Due to this, ERC-20 token contracts that burn tokens commonly
transfer() the tokens to the 0 address in lieu of real burning.
ERC-20 provides a great basis to build token contracts, but is not without its faults. The ERC-223 proposal provides additional features and safety measures, but is not compatible with ERC-20. Token contracts built today should continue to follow ERC-20 and developers should keep track of and contribute to the ERC-223 proposal.