ERC-20 Contract Walk-Through
Introduction
One of the most common uses for Ethereum is for a group to create a tradable token, in a sense their own currency. These tokens typically follow a standard, ERC-20. This standard makes it possible to write tools, such as liquidity pools and wallets, that work with all ERC-20 tokens. In this article we will analyze the OpenZeppelin Solidity ERC20 implementation(opens in a new tab), as well as the interface definition(opens in a new tab).
This is annotated source code. If you want to implement ERC-20, read this tutorial(opens in a new tab).
The Interface
The purpose of a standard like ERC-20 is to allow many tokens implementations that are interoperable across applications, like wallets and decentralized exchanges. To achieve that, we create an interface(opens in a new tab). Any code that needs to use the token contract can use the same definitions in the interface and be compatible with all token contracts that use it, whether it is a wallet such as MetaMask, a dapp such as etherscan.io, or a different contract such as liquidity pool.
If you are an experienced programmer, you probably remember seeing similar constructs in Java(opens in a new tab) or even in C header files(opens in a new tab).
This is a definition of the ERC-20 Interface(opens in a new tab) from OpenZeppelin. It is a translation of the human readable standard(opens in a new tab) into Solidity code. Of course, the interface itself does not define how to do anything. That is explained in the contract source code below.
1// SPDX-License-Identifier: MIT2Kopyala
Solidity files are supposed to includes a license identifier. You can see the list of licenses here(opens in a new tab). If you need a different license, just explain it in the comments.
1pragma solidity >=0.6.0 <0.8.0;2Kopyala
The Solidity language is still evolving quickly, and new versions may not be compatible with old code (see here(opens in a new tab)). Therefore, it is a good idea to specify not just a minimum version of the language, but also a maximum version, the latest with which you tested the code.
1/**2 * @dev Interface of the ERC20 standard as defined in the EIP.3 */4Kopyala
The @dev
in the comment is part of the NatSpec format(opens in a new tab), used to produce
documentation from the source code.
1interface IERC20 {2Kopyala
By convention, interface names start with I
.
1 /**2 * @dev Returns the amount of tokens in existence.3 */4 function totalSupply() external view returns (uint256);5Kopyala
This function is external
, meaning it can only be called from outside the contract(opens in a new tab).
It returns the total supply of tokens in the contract. This value is returned using the most common type in Ethereum, unsigned 256 bits (256 bits is the
native word size of the EVM). This function is also a view
, which means that it does not change the state, so it can be executed on a single node instead of having
every node in the blockchain run it. This kind of function does not generate a transaction and does not cost gas.
Note: In theory it might appear that a contract's creator could cheat by returning a smaller total supply than the real value, making each token appear more valuable than it actually is. However, that fear ignores the true nature of the blockchain. Everything that happens on the blockchain can be verified by every node. To achieve this, every contract's machine language code and storage is available on every node. While you are not required to publish the Solidity code for your contract, nobody would take you seriously unless you publish the source code and the version of Solidity with which it was complied, so it can be verified against the machine language code you provided. For example, see this contract(opens in a new tab).
1 /**2 * @dev Returns the amount of tokens owned by `account`.3 */4 function balanceOf(address account) external view returns (uint256);5Kopyala
As the name says, balanceOf
returns the balance of an account. Ethereum accounts are identified in Solidity using the address
type, which holds 160 bits.
It is also external
and view
.
1 /**2 * @dev Moves `amount` tokens from the caller's account to `recipient`.3 *4 * Returns a boolean value indicating whether the operation succeeded.5 *6 * Emits a {Transfer} event.7 */8 function transfer(address recipient, uint256 amount) external returns (bool);9Kopyala
The transfer
function transfers a tokens from the caller to a different address. This involves a change of state, so it isn't a view
.
When a user calls this function it creates a transaction and costs gas. It also emits an event, Transfer
, to inform everybody on
the blockchain of the event.
The function has two types of output for two different types of callers:
- Users that call the function directly from a user interface. Typically the user submits a transaction
and does not wait for a response, which could take an indefinite amount of time. The user can see what happened
by looking for the transaction receipt (which is identified by the transaction hash) or by looking for the
Transfer
event. - Other contracts, which call the function as part of an overall transaction. Those contracts get the result immediately, because they run in the same transaction, so they can use the function return value.
The same type of output is created by the other functions that change the contract's state.
Allowances permit an account to spend some tokens that belong to a different owner. This is useful, for example, for contracts that act as sellers. Contracts cannot monitor for events, so if a buyer were to transfer tokens to the seller contract directly that contract wouldn't know it was paid. Instead, the buyer permits the seller contract to spend a certain amount, and the seller transfers that amount. This is done through a function the seller contract calls, so the seller contract can know if it was successful.
1 /**2 * @dev Returns the remaining number of tokens that `spender` will be3 * allowed to spend on behalf of `owner` through {transferFrom}. This is4 * zero by default.5 *6 * This value changes when {approve} or {transferFrom} are called.7 */8 function allowance(address owner, address spender) external view returns (uint256);9Kopyala
The allowance
function lets anybody query to see what is the allowance that one
address (owner
) lets another address (spender
) spend.
1 /**2 * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.3 *4 * Returns a boolean value indicating whether the operation succeeded.5 *6 * IMPORTANT: Beware that changing an allowance with this method brings the risk7 * that someone may use both the old and the new allowance by unfortunate8 * transaction ordering. One possible solution to mitigate this race9 * condition is to first reduce the spender's allowance to 0 and set the10 * desired value afterwards:11 * https://github.com/ethereum/EIPs/issues/20#issuecomment-26352472912 *13 * Emits an {Approval} event.14 */15 function approve(address spender, uint256 amount) external returns (bool);16Hamısını göstərKopyala
The approve
function creates an allowance. Make sure to read the message about
how it can be abused. In Ethereum you control the order of your own transactions,
but you cannot control the order in which other people's transactions will
be executed, unless you don't submit your own transaction until you see the
other side's transaction had happened.
1 /**2 * @dev Moves `amount` tokens from `sender` to `recipient` using the3 * allowance mechanism. `amount` is then deducted from the caller's4 * allowance.5 *6 * Returns a boolean value indicating whether the operation succeeded.7 *8 * Emits a {Transfer} event.9 */10 function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);11Hamısını göstərKopyala
Finally, transferFrom
is used by the spender to actually spend the allowance.
12 /**3 * @dev Emitted when `value` tokens are moved from one account (`from`) to4 * another (`to`).5 *6 * Note that `value` may be zero.7 */8 event Transfer(address indexed from, address indexed to, uint256 value);910 /**11 * @dev Emitted when the allowance of a `spender` for an `owner` is set by12 * a call to {approve}. `value` is the new allowance.13 */14 event Approval(address indexed owner, address indexed spender, uint256 value);15}16Hamısını göstərKopyala
These events are emitted when the state of the ERC-20 contract changes.
The Actual Contract
This is the actual contract that implements the ERC-20 standard, taken from here(opens in a new tab). It is not meant to be used as-is, but you can inherit(opens in a new tab) from it to extend it to something usable.
1// SPDX-License-Identifier: MIT2pragma solidity >=0.6.0 <0.8.0;3Kopyala
Import Statements
In addition to the interface definitions above, the contract definition imports two other files:
12import "../../GSN/Context.sol";3import "./IERC20.sol";4import "../../math/SafeMath.sol";5Kopyala
GSN/Context.sol
is the definitions required to use OpenGSN(opens in a new tab), a system that allows users without ether to use the blockchain. Note that this is an old version, if you want to integrate with OpenGSN use this tutorial(opens in a new tab).- The SafeMath library(opens in a new tab), which is used to make addition and subtraction without overflows. This is necessary because otherwise a person might somehow have one token, spend two tokens, and then have 2^256-1 tokens.
This comment explains the purpose of the contract.
1/**2 * @dev Implementation of the {IERC20} interface.3 *4 * This implementation is agnostic to the way tokens are created. This means5 * that a supply mechanism has to be added in a derived contract using {_mint}.6 * For a generic mechanism see {ERC20PresetMinterPauser}.7 *8 * TIP: For a detailed writeup see our guide9 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How10 * to implement supply mechanisms].11 *12 * We have followed general OpenZeppelin guidelines: functions revert instead13 * of returning `false` on failure. This behavior is nonetheless conventional14 * and does not conflict with the expectations of ERC20 applications.15 *16 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.17 * This allows applications to reconstruct the allowance for all accounts just18 * by listening to said events. Other implementations of the EIP may not emit19 * these events, as it isn't required by the specification.20 *21 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}22 * functions have been added to mitigate the well-known issues around setting23 * allowances. See {IERC20-approve}.24 */2526Hamısını göstərKopyala
Contract Definition
1contract ERC20 is Context, IERC20 {2Kopyala
This line specifies the inheritance, in this case from IERC20
from above and Context
, for OpenGSN.
12 using SafeMath for uint256;34Kopyala
This line attaches the SafeMath
library to the uint256
type. You can find this library
here(opens in a new tab).
Variable Definitions
These definitions specify the contract's state variables. There variables are declared private
, but
that only means that other contracts on the blockchain can't read them. There are no
secrets on the blockchain, the software on every node has the state of every contract
at every block. By convention, state variables are named _<something>
.
The first two variables are mappings(opens in a new tab), meaning they behave roughly the same as associative arrays(opens in a new tab), except that the keys are numeric values. Storage is only allocated for entries that have values different from the default (zero).
1 mapping (address => uint256) private _balances;2Kopyala
The first mapping, _balances
, is addresses and their respective balances of this token. To access
the balance, use this syntax: _balances[<address>]
.
1 mapping (address => mapping (address => uint256)) private _allowances;2Kopyala
This variable, _allowances
, stores the allowances explained earlier. The first index is the owner
of the tokens, and the second is the contract with the allowance. To access the amount address A can
spend from address B's account, use _allowances[B][A]
.
1 uint256 private _totalSupply;2