Tokens on Ethereum

Blockchain tokens existed before Ethereum. In some ways, the first blockchain currency, Bitcoin, is a token itself. Many token platforms were also developed on Bitcoin and other cryptocurrencies before Ethereum. However, the introduction of the first token standard on Ethereum led to an explosion of tokens.

Vitalik Buterin suggested tokens as one of the most obvious and useful applications of a generalized programmable blockchain such as Ethereum. In fact, in the first year of Ethereum, it was common to see Vitalik and others wearing T-shirts emblazoned with the Ethereum logo and a smart contract sample on the back. There were several variations of this T-shirt, but the most common showed an implementation of a token.

Before we delve into the details of creating tokens on Ethereum, it is important to have an overview of how tokens work on Ethereum. Tokens are different from ether because the Ethereum protocol does not know anything about them. Sending ether is an intrinsic action of the Ethereum platform, but sending or even owning tokens is not. The ether balance of Ethereum accounts is handled at the protocol level, whereas the token balance of Ethereum accounts is handled at the smart contract level. In order to create a new token on Ethereum, you must create a new smart contract. Once deployed, the smart contract handles everything, including ownership, transfers, and access rights. You can write your smart contract to perform all the necessary actions any way you want, but it is probably wisest to follow an existing standard. We will look at such standards next. We discuss the pros and cons of the following standards at the end of the chapter.

The ERC20 Token Standard

The first standard was introduced in November 2015 by Fabian Vogelsteller as an Ethereum Request for Comments (ERC). It was automatically assigned GitHub issue number 20, giving rise to the name “ERC20 token.” The vast majority of tokens are currently based on the ERC20 standard. The ERC20 request for comments eventually became Ethereum Improvement Proposal 20 (EIP-20), but it is mostly still referred to by the original name, ERC20.

ERC20 is a standard for fungible tokens, meaning that different units of an ERC20 token are interchangeable and have no unique properties.

The ERC20 standard defines a common interface for contracts implementing a token, such that any compatible token can be accessed and used in the same way. The interface consists of a number of functions that must be present in every implementation of the standard, as well as some optional functions and attributes that may be added by developers.

ERC20 required functions and events

An ERC20-compliant token contract must provide at least the following functions and events:

totalSupply

Returns the total units of this token that currently exist. ERC20 tokens can have a fixed or a variable supply.

balanceOf

Given an address, returns the token balance of that address.

transfer

Given an address and amount, transfers that amount of tokens to that address, from the balance of the address that executed the transfer.

transferFrom

Given a sender, recipient, and amount, transfers tokens from one account to another. Used in combination with approve.

approve

Given a recipient address and amount, authorizes that address to execute several transfers up to that amount, from the account that issued the approval.

allowance

Given an owner address and a spender address, returns the remaining amount that the spender is approved to withdraw from the owner.

Transfer

Event triggered upon a successful transfer (call to transfer or transferFrom) (even for zero-value transfers).

Approval

Event logged upon a successful call to approve.

ERC20 optional functions

In addition to the required functions listed in the previous section, the following optional functions are also defined by the standard:

name

Returns the human-readable name (e.g., “US Dollars”) of the token.

symbol

Returns a human-readable symbol (e.g., “USD”) for the token.

decimals

Returns the number of decimals used to divide token amounts. For example, if decimals is 2, then the token amount is divided by 100 to get its user representation.

The ERC20 interface defined in Solidity

Here’s what an ERC20 interface specification looks like in Solidity:

  1. contract ERC20 {
  2. function totalSupply() constant returns (uint theTotalSupply);
  3. function balanceOf(address _owner) constant returns (uint balance);
  4. function transfer(address _to, uint _value) returns (bool success);
  5. function transferFrom(address _from, address _to, uint _value) returns
  6. (bool success);
  7. function approve(address _spender, uint _value) returns (bool success);
  8. function allowance(address _owner, address _spender) constant returns
  9. (uint remaining);
  10. event Transfer(address indexed _from, address indexed _to, uint _value);
  11. event Approval(address indexed _owner, address indexed _spender, uint _value);
  12. }
ERC20 data structures

If you examine any ERC20 implementation you will see that it contains two data structures, one to track balances and one to track allowances. In Solidity, they are implemented with a data mapping.

The first data mapping implements an internal table of token balances, by owner. This allows the token contract to keep track of who owns the tokens. Each transfer is a deduction from one balance and an addition to another balance:

  1. mapping(address => uint256) balances;

The second data structure is a data mapping of allowances. As we will see in the next section, with ERC20 tokens an owner of a token can delegate authority to a spender, allowing them to spend a specific amount (allowance) from the owner’s balance. The ERC20 contract keeps track of the allowances with a two-dimensional mapping, with the primary key being the address of the token owner, mapping to a spender address and an allowance amount:

  1. mapping (address => mapping (address => uint256)) public allowed;
ERC20 workflows: “transfer” and “approve & transferFrom”

The ERC20 token standard has two transfer functions. You might be wondering why.

ERC20 allows for two different workflows. The first is a single-transaction, straightforward workflow using the transfer function. This workflow is the one used by wallets to send tokens to other wallets. The vast majority of token transactions happen with the transfer workflow.

Executing the transfer contract is very simple. If Alice wants to send 10 tokens to Bob, her wallet sends a transaction to the token contract’s address, calling the transfer function with Bob’s address and 10 as the arguments. The token contract adjusts Alice’s balance (–10) and Bob’s balance (+10) and issues a Transfer event.

The second workflow is a two-transaction workflow that uses approve followed by transferFrom. This workflow allows a token owner to delegate their control to another address. It is most often used to delegate control to a contract for distribution of tokens, but it can also be used by exchanges.

For example, if a company is selling tokens for an ICO, they can approve a crowdsale contract address to distribute a certain amount of tokens. The crowdsale contract can then transferFrom the token contract owner’s balance to each buyer of the token, as illustrated in The two-step approve & transferFrom workflow of ERC20 tokens.

Note

An Initial Coin Offering (ICO) is a crowdfunding mechanism used by companies and organizations to raise money by selling tokens. The term is derived from Initial Public Offering (IPO), which is the process by which a public company offers shares for sale to investors on a stock exchange. Unlike the highly regulated IPO markets, ICOs are open, global, and messy. The examples and explanations of ICOs in this book are not an endorsement of this type of fundraising.

The two-step approve & transferFrom workflow of ERC20 tokens

Figure 1. The two-step approve & transferFrom workflow of ERC20 tokens

For the approve & transferFrom workflow, two transactions are needed. Let’s say that Alice wants to allow the AliceICO contract to sell 50% of all the AliceCoin tokens to buyers like Bob and Charlie. First, Alice launches the AliceCoin ERC20 contract, issuing all the AliceCoin to her own address. Then, Alice launches the AliceICO contract that can sell tokens for ether. Next, Alice initiates the approve & transferFrom workflow. She sends a transaction to the AliceCoin contract, calling approve with the address of the AliceICO contract and 50% of the totalSupply as arguments. This will trigger the Approval event. Now, the AliceICO contract can sell AliceCoin.

When the AliceICO contract receives ether from Bob, it needs to send some AliceCoin to Bob in return. Within the AliceICO contract is an exchange rate between AliceCoin and ether. The exchange rate that Alice set when she created the AliceICO contract determines how many tokens Bob will receive for the amount of ether sent to the AliceICO contract. When the AliceICO contract calls the AliceCoin transferFrom function, it sets Alice’s address as the sender and Bob’s address as the recipient, and uses the exchange rate to determine how many AliceCoin tokens will be transferred to Bob in the value field. The AliceCoin contract transfers the balance from Alice’s address to Bob’s address and triggers a Transfer event. The AliceICO contract can call transferFrom an unlimited number of times, as long as it doesn’t exceed the approval limit Alice set. The AliceICO contract can keep track of how many AliceCoin tokens it can sell by calling the allowance function.

ERC20 implementations

While it is possible to implement an ERC20-compatible token in about 30 lines of Solidity code, most implementations are more complex. This is to account for potential security vulnerabilities. There are two implementations mentioned in the EIP-20 standard:

Consensys EIP20

A simple and easy-to-read implementation of an ERC20-compatible token.

OpenZeppelin StandardToken

This implementation is ERC20-compatible, with additional security precautions. It forms the basis of OpenZeppelin libraries implementing more complex ERC20-compatible tokens with fundraising caps, auctions, vesting schedules, and other features.

Launching Our Own ERC20 Token

Let’s create and launch our own token. For this example, we will use the Truffle framework. The example assumes you have already installed truffle and configured it, and are familiar with its basic operation (for details, see [truffle]).

We will call our token “Mastering Ethereum Token,” with the symbol “MET.”

Note

You can find this example in the book’s GitHub repository.

First, let’s create and initialize a Truffle project directory. Run these four commands and accept the default answers to any questions:

  1. $ mkdir METoken
  2. $ cd METoken
  3. METoken $ truffle init
  4. METoken $ npm init

You should now have the following directory structure:

  1. METoken/
  2. +---- contracts
  3. | `---- Migrations.sol
  4. +---- migrations
  5. | `---- 1_initial_migration.js
  6. +---- package.json
  7. +---- test
  8. `---- truffle-config.js

Edit the truffle-config.js configuration file to set up your Truffle environment, or copy the latter from the repository.

If you use the example truffle-config.js, remember to create a file .env in the METoken folder containing your test private keys for testing and deployment on public Ethereum test networks, such as Ropsten or Kovan. You can export your test network private key from MetaMask.

After that your directory should look like:

  1. METoken/
  2. +---- contracts
  3. | `---- Migrations.sol
  4. +---- migrations
  5. | `---- 1_initial_migration.js
  6. +---- package.json
  7. +---- test
  8. +---- truffle-config.js
  9. `---- .env *new file*
Warning

Only use test keys or test mnemonics that are not used to hold funds on the main Ethereum network. Never use keys that hold real money for testing.

For our example, we will import the OpenZeppelin library, which implements some important security checks and is easy to extend:

  1. $ npm install openzeppelin-solidity@1.12.0
  2. + openzeppelin-solidity@1.12.0
  3. added 1 package from 1 contributor and audited 2381 packages in 4.074s

The openzeppelin-solidity package will add about 250 files under the node_modules directory. The OpenZeppelin library includes a lot more than the ERC20 token, but we will only use a small part of it.

Next, let’s write our token contract. Create a new file, METoken.sol, and copy the example code from GitHub.

Our contract, shown in METoken.sol: A Solidity contract implementing an ERC20 token, is very simple, as it inherits all its functionality from the OpenZeppelin library.

Example 1. METoken.sol: A Solidity contract implementing an ERC20 token

  1. link:code/truffle/METoken/contracts/METoken.sol[]

Here, we are defining the optional variables name, symbol, and decimals. We also define an _initial_supply variable, set to 21 million tokens; with two decimals of subdivision that gives 2.1 billion total units. In the contract’s initialization (constructor) function we set the totalSupply to be equal to _initial_supply and allocate all of the _initial_supply to the balance of the account (msg.sender) that creates the METoken contract.

We now use truffle to compile the METoken code:

  1. $ truffle compile
  2. Compiling ./contracts/METoken.sol...
  3. Compiling ./contracts/Migrations.sol...
  4. Compiling openzeppelin-solidity/contracts/math/SafeMath.sol...
  5. Compiling openzeppelin-solidity/contracts/token/ERC20/BasicToken.sol...
  6. Compiling openzeppelin-solidity/contracts/token/ERC20/ERC20.sol...
  7. Compiling openzeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol...
  8. Compiling openzeppelin-solidity/contracts/token/ERC20/StandardToken.sol...

As you can see, truffle incorporates necessary dependencies from the OpenZeppelin libraries and compiles those contracts too.

Let’s set up a migration script to deploy the METoken contract. Create a new file called 2_deploy_contracts.js, in the METoken/migrations folder. Copy the contents from the example in the GitHub repository:

2_deploy_contracts: Migration to deploy METoken

  1. link:code/truffle/METoken/migrations/2_deploy_contracts.js[]

Before we deploy on one of the Ethereum test networks, let’s start a local blockchain to test everything. Start the ganache blockchain, either from the command line with ganache-cli or from the graphical user interface.

Once ganache is started, we can deploy our METoken contract and see if everything works as expected:

  1. $ truffle migrate --network ganache
  2. Using network 'ganache'.
  3. Running migration: 1_initial_migration.js
  4. Deploying Migrations...
  5. ... 0xb2e90a056dc6ad8e654683921fc613c796a03b89df6760ec1db1084ea4a084eb
  6. Migrations: 0x8cdaf0cd259887258bc13a92c0a6da92698644c0
  7. Saving successful migration to network...
  8. ... 0xd7bc86d31bee32fa3988f1c1eabce403a1b5d570340a3a9cdba53a472ee8c956
  9. Saving artifacts...
  10. Running migration: 2_deploy_contracts.js
  11. Deploying METoken...
  12. ... 0xbe9290d59678b412e60ed6aefedb17364f4ad2977cfb2076b9b8ad415c5dc9f0
  13. METoken: 0x345ca3e014aaf5dca488057592ee47305d9b3e10
  14. Saving successful migration to network...
  15. ... 0xf36163615f41ef7ed8f4a8f192149a0bf633fe1a2398ce001bf44c43dc7bdda0
  16. Saving artifacts...

On the ganache console, we should see that our deployment has created four new transactions, as depicted in METoken deployment on ganache.

METoken deployment on Ganache

Figure 2. METoken deployment on ganache

Interacting with METoken using the Truffle console

We can interact with our contract on the ganache blockchain using the Truffle console. This is an interactive JavaScript environment that provides access to the Truffle environment and, via web3, to the blockchain. In this case, we will connect the Truffle console to the ganache blockchain:

  1. $ truffle console --network ganache
  2. truffle(ganache)>

The truffle(ganache)> prompt shows that we are connected to the ganache blockchain and are ready to type our commands. The Truffle console supports all the truffle commands, so we could compile and migrate from the console. We’ve already run those commands, so let’s go directly to the contract itself. The METoken contract exists as a JavaScript object within the Truffle environment. Type **METoken** at the prompt and it will dump the entire contract definition:

  1. truffle(ganache)> METoken
  2. { [Function: TruffleContract]
  3. _static_methods:
  4. [...]
  5. currentProvider:
  6. HttpProvider {
  7. host: 'http://localhost:7545',
  8. timeout: 0,
  9. user: undefined,
  10. password: undefined,
  11. headers: undefined,
  12. send: [Function],
  13. sendAsync: [Function],
  14. _alreadyWrapped: true },
  15. network_id: '5777' }

The METoken object also exposes several attributes, such as the address of the contract (as deployed by the migrate command):

  1. truffle(ganache)> METoken.address
  2. '0x345ca3e014aaf5dca488057592ee47305d9b3e10'

If we want to interact with the deployed contract, we have to use an asynchronous call, in the form of a JavaScript “promise.” We use the deployed function to get the contract instance and then call the totalSupply function:

  1. truffle(ganache)> METoken.deployed().then(instance => instance.totalSupply())
  2. BigNumber { s: 1, e: 9, c: [ 2100000000 ] }

Next, let’s use the accounts created by ganache to check our METoken balance and send some METoken to another address. First, let’s get the account addresses:

  1. truffle(ganache)> let accounts
  2. undefined
  3. truffle(ganache)> web3.eth.getAccounts((err,res) => { accounts = res })
  4. undefined
  5. truffle(ganache)> accounts[0]
  6. '0x627306090abab3a6e1400e9345bc60c78a8bef57'

The accounts list now contains all the accounts created by ganache, and accounts[0] is the account that deployed the METoken contract. It should have a balance of METoken, because our METoken constructor gives the entire token supply to the address that created it. Let’s check:

  1. truffle(ganache)> METoken.deployed().then(instance =>
  2. { instance.balanceOf(accounts[0]).then(console.log) })
  3. undefined
  4. truffle(ganache)> BigNumber { s: 1, e: 9, c: [ 2100000000 ] }

Finally, let’s transfer 1000.00 METoken from accounts[0] to accounts[1], by calling the contract’s transfer function:

  1. truffle(ganache)> METoken.deployed().then(instance =>
  2. { instance.transfer(accounts[1], 100000) })
  3. undefined
  4. truffle(ganache)> METoken.deployed().then(instance =>
  5. { instance.balanceOf(accounts[0]).then(console.log) })
  6. undefined
  7. truffle(ganache)> BigNumber { s: 1, e: 9, c: [ 2099900000 ] }
  8. undefined
  9. truffle(ganache)> METoken.deployed().then(instance =>
  10. { instance.balanceOf(accounts[1]).then(console.log) })
  11. undefined
  12. truffle(ganache)> BigNumber { s: 1, e: 5, c: [ 100000 ] }
Tip

METoken has 2 decimals of precision, meaning that 1 METoken is 100 units in the contract. When we transfer 1,000 METoken, we specify the value as 100000 in the call to the transfer function.

As you can see, in the console, accounts[0] now has 20,999,000 MET, and accounts[1] has 1,000 MET.

If you switch to the ganache graphical user interface, as shown in METoken transfer on ganache, you will see the transaction that called the transfer function.

METoken transfer on Ganache

Figure 3. METoken transfer on ganache

Sending ERC20 tokens to contract addresses

So far, we’ve set up an ERC20 token and transferred some tokens from one account to another. All the accounts we used for these demonstrations are externally owned accounts, meaning they are controlled by a private key, not a contract. What happens if we send MET to a contract address? Let’s find out!

First, let’s deploy another contract into our test environment. For this example, we will use our first contract, Faucet.sol. Let’s add it to the METoken project by copying it to the contracts directory. Our directory should look like this:

  1. METoken/
  2. +---- contracts
  3. | +---- Faucet.sol
  4. | +---- METoken.sol
  5. | `---- Migrations.sol

We’ll also add a migration, to deploy Faucet separately from METoken:

  1. var Faucet = artifacts.require("Faucet");
  2. module.exports = function(deployer) {
  3. // Deploy the Faucet contract as our only task
  4. deployer.deploy(Faucet);
  5. };

Let’s compile and migrate the contracts from the Truffle console:

  1. $ truffle console --network ganache
  2. truffle(ganache)> compile
  3. Compiling ./contracts/Faucet.sol...
  4. Writing artifacts to ./build/contracts
  5. truffle(ganache)> migrate
  6. Using network 'ganache'.
  7. Running migration: 1_initial_migration.js
  8. Deploying Migrations...
  9. ... 0x89f6a7bd2a596829c60a483ec99665c7af71e68c77a417fab503c394fcd7a0c9
  10. Migrations: 0xa1ccce36fb823810e729dce293b75f40fb6ea9c9
  11. Saving artifacts...
  12. Running migration: 2_deploy_contracts.js
  13. Replacing METoken...
  14. ... 0x28d0da26f48765f67e133e99dd275fac6a25fdfec6594060fd1a0e09a99b44ba
  15. METoken: 0x7d6bf9d5914d37bcba9d46df7107e71c59f3791f
  16. Saving artifacts...
  17. Running migration: 3_deploy_faucet.js
  18. Deploying Faucet...
  19. ... 0x6fbf283bcc97d7c52d92fd91f6ac02d565f5fded483a6a0f824f66edc6fa90c3
  20. Faucet: 0xb18a42e9468f7f1342fa3c329ec339f254bc7524
  21. Saving artifacts...

Great. Now let’s send some MET to the Faucet contract:

  1. truffle(ganache)> METoken.deployed().then(instance =>
  2. { instance.transfer(Faucet.address, 100000) })
  3. truffle(ganache)> METoken.deployed().then(instance =>
  4. { instance.balanceOf(Faucet.address).then(console.log)})
  5. truffle(ganache)> BigNumber { s: 1, e: 5, c: [ 100000 ] }

Alright, we have transferred 1,000 MET to the Faucet contract. Now, how do we withdraw those tokens?

Remember, Faucet.sol is a pretty simple contract. It only has one function, withdraw, which is for withdrawing ether. It doesn’t have a function for withdrawing MET, or any other ERC20 token. If we use withdraw it will try to send ether, but since Faucet doesn’t have a balance of ether yet, it will fail.

The METoken contract knows that Faucet has a balance, but the only way that it can transfer that balance is if it receives a transfer call from the address of the contract. Somehow we need to make the Faucet contract call the transfer function in METoken.

If you’re wondering what to do next, don’t. There is no solution to this problem. The MET sent to Faucet is stuck, forever. Only the Faucet contract can transfer it, and the Faucet contract doesn’t have code to call the transfer function of an ERC20 token contract.

Perhaps you anticipated this problem. Most likely, you didn’t. In fact, neither did hundreds of Ethereum users who accidentally transferred various tokens to contracts that didn’t have any ERC20 capability. According to some estimates, tokens worth more than roughly $2.5 million USD (at the time of writing) have gotten “stuck” like this and are lost forever.

One of the ways that users of ERC20 tokens can inadvertently lose their tokens in a transfer, is when they attempt to transfer to an exchange or another service. They copy an Ethereum address from the website of an exchange, thinking they can simply send tokens to it. However, many exchanges publish receiving addresses that are actually contracts! These contracts are only meant to receive ether, not ERC20 tokens, most often sweeping all funds sent to them to “cold storage” or another centralized wallet. Despite the many warnings saying “do not send tokens to this address,” lots of tokens are lost this way.

Demonstrating the “approve & transferFrom” workflow

Our Faucet contract couldn’t handle ERC20 tokens. Sending tokens to it using the transfer function resulted in the loss of those tokens. Let’s rewrite the contract now and make it handle ERC20 tokens. Specifically, we will turn it into a faucet that gives out MET to anyone who asks.

For this example, we’ll make a copy of the truffle project directory (we’ll call it METoken_METFaucet), initialize truffle and npm, install the OpenZeppelin dependencies, and copy the METoken.sol contract. See our first example, in Launching Our Own ERC20 Token, for the detailed instructions.

Our new faucet contract, METFaucet.sol, will look like METFaucet.sol: A faucet for METoken.

Example 2. METFaucet.sol: A faucet for METoken

  1. link:code/truffle/METoken_METFaucet/contracts/METFaucet.sol[]

We’ve made quite a few changes to the basic Faucet example. Since METFaucet will use the transferFrom function in METoken, it will need two additional variables. One will hold the address of the deployed METoken contract. The other will hold the address of the owner of the MET, who will approve the faucet withdrawals. The METFaucet contract will call METoken.transferFrom and instruct it to move MET from the owner to the address where the faucet withdrawal request came from.

We declare these two variables here:

  1. StandardToken public METoken;
  2. address public METOwner;

Since our faucet needs to be initialized with the correct addresses for METoken and METOwner, we need to declare a custom constructor:

  1. // METFaucet constructor - provide the address of the METoken contract and
  2. // the owner address we will be approved to transferFrom
  3. function METFaucet(address _METoken, address _METOwner) public {
  4. // Initialize the METoken from the address provided
  5. METoken = StandardToken(_METoken);
  6. METOwner = _METOwner;
  7. }

The next change is to the withdraw function. Instead of calling transfer, METFaucet uses the transferFrom function in METoken and asks METoken to transfer MET to the faucet recipient:

  1. // Use the transferFrom function of METoken
  2. METoken.transferFrom(METOwner, msg.sender, withdraw_amount);

Finally, since our faucet no longer sends ether, we should probably prevent anyone from sending ether to METFaucet, as we wouldn’t want it to get stuck. We change the fallback payable function to reject incoming ether, using the revert function to revert any incoming payments:

  1. // REJECT any incoming ether
  2. function () external payable { revert(); }

Now that our METFaucet.sol code is ready, we need to modify the migration script to deploy it. This migration script will be a bit more complex, as METFaucet depends on the address of METoken. We will use a JavaScript promise to deploy the two contracts in sequence. Create 2_deploy_contracts.js as follows:

  1. var METoken = artifacts.require("METoken");
  2. var METFaucet = artifacts.require("METFaucet");
  3. var owner = web3.eth.accounts[0];
  4. module.exports = function(deployer) {
  5. // Deploy the METoken contract first
  6. deployer.deploy(METoken, {from: owner}).then(function() {
  7. // Then deploy METFaucet and pass the address of METoken and the
  8. // address of the owner of all the MET who will approve METFaucet
  9. return deployer.deploy(METFaucet, METoken.address, owner);
  10. });
  11. }

Now, we can test everything in the Truffle console. First, we use migrate to deploy the contracts. When METoken is deployed it will allocate all the MET to the account that created it, web3.eth.accounts[0]. Then, we call the approve function in METoken to approve METFaucet to send up to 1,000 MET on behalf of web3.eth.accounts[0]. Finally, to test our faucet, we call METFaucet.withdraw from web3.eth.accounts[1] and try to withdraw 10 MET. Here are the console commands:

  1. $ truffle console --network ganache
  2. truffle(ganache)> migrate
  3. Using network 'ganache'.
  4. Running migration: 1_initial_migration.js
  5. Deploying Migrations...
  6. ... 0x79352b43e18cc46b023a779e9a0d16b30f127bfa40266c02f9871d63c26542c7
  7. Migrations: 0xaa588d3737b611bafd7bd713445b314bd453a5c8
  8. Saving artifacts...
  9. Running migration: 2_deploy_contracts.js
  10. Replacing METoken...
  11. ... 0xc42a57f22cddf95f6f8c19d794c8af3b2491f568b38b96fef15b13b6e8bfff21
  12. METoken: 0xf204a4ef082f5c04bb89f7d5e6568b796096735a
  13. Replacing METFaucet...
  14. ... 0xd9615cae2fa4f1e8a377de87f86162832cf4d31098779e6e00df1ae7f1b7f864
  15. METFaucet: 0x75c35c980c0d37ef46df04d31a140b65503c0eed
  16. Saving artifacts...
  17. truffle(ganache)> METoken.deployed().then(instance =>
  18. { instance.approve(METFaucet.address, 100000) })
  19. truffle(ganache)> METoken.deployed().then(instance =>
  20. { instance.balanceOf(web3.eth.accounts[1]).then(console.log) })
  21. truffle(ganache)> BigNumber { s: 1, e: 0, c: [ 0 ] }
  22. truffle(ganache)> METFaucet.deployed().then(instance =>
  23. { instance.withdraw(1000, {from:web3.eth.accounts[1]}) } )
  24. truffle(ganache)> METoken.deployed().then(instance =>
  25. { instance.balanceOf(web3.eth.accounts[1]).then(console.log) })
  26. truffle(ganache)> BigNumber { s: 1, e: 3, c: [ 1000 ] }

As you can see from the results, we can use the approve & transferFrom workflow to authorize one contract to transfer tokens defined in another contract. If properly used, ERC20 tokens can be used by EOAs and other contracts.

However, the burden of managing ERC20 tokens correctly is pushed to the user interface. If a user incorrectly attempts to transfer ERC20 tokens to a contract address and that contract is not equipped to receive ERC20 tokens, the tokens will be lost.

Issues with ERC20 Tokens

The adoption of the ERC20 token standard has been truly explosive. Thousands of tokens have been launched, both to experiment with new capabilities and to raise funds in various “crowdfund” auctions and ICOs. However, there are some potential pitfalls, as we saw with the issue of transferring tokens to contract addresses.

One of the less obvious issues with ERC20 tokens is that they expose subtle differences between tokens and ether itself. Where ether is transferred by a transaction that has a recipient address as its destination, token transfers occur within the specific token contract state and have the token contract as their destination, not the recipient’s address. The token contract tracks balances and issues events. In a token transfer, no transaction is actually sent to the recipient of the token. Instead, the recipient’s address is added to a map within the token contract itself. A transaction sending ether to an address changes the state of an address. A transaction transferring a token to an address only changes the state of the token contract, not the state of the recipient address. Even a wallet that has support for ERC20 tokens does not become aware of a token balance unless the user explicitly adds a specific token contract to “watch.” Some wallets watch the most popular token contracts to detect balances held by addresses they control, but that’s limited to a small fraction of existing ERC20 contracts.

In fact, it’s unlikely that a user would want to track all balances in all possible ERC20 token contracts. Many ERC20 tokens are more like email spam than usable tokens. They automatically create balances for accounts that have ether activity, in order to attract users. If you have an Ethereum address with a long history of activity, especially if it was created in the presale, you will find it full of “junk” tokens that appeared out of nowhere. Of course, the address isn’t really full of tokens; it’s the token contracts that have your address in them. You only see these balances if these token contracts are being watched by the block explorer or wallet you use to view your address.

Tokens don’t behave the same way as ether. Ether is sent with the send function and accepted by any payable function in a contract or any externally owned address. Tokens are sent using transfer or approve & transferFrom functions that exist only in the ERC20 contract, and do not (at least in ERC20) trigger any payable functions in a recipient contract. Tokens are meant to function just like a cryptocurrency such as ether, but they come with certain differences that break that illusion.

Consider another issue. To send ether or use any Ethereum contract you need ether to pay for gas. To send tokens, you also need ether. You cannot pay for a transaction’s gas with a token and the token contract can’t pay for the gas for you. This may change at some point in the distant future, but in the meantime this can cause some rather strange user experiences. For example, let’s say you use an exchange or ShapeShift to convert some bitcoin to a token. You “receive” the token in a wallet that tracks that token’s contract and shows your balance. It looks the same as any of the other cryptocurrencies you have in your wallet. Try sending the token, though, and your wallet will inform you that you need ether to do that. You might be confused—after all, you didn’t need ether to receive the token. Perhaps you have no ether. Perhaps you didn’t even know the token was an ERC20 token on Ethereum; maybe you thought it was a cryptocurrency with its own blockchain. The illusion just broke.

Some of these issues are specific to ERC20 tokens. Others are more general issues that relate to abstraction and interface boundaries within Ethereum. Some can be solved by changing the token interface, while others may need changes to fundamental structures within Ethereum (such as the distinction between EOAs and contracts, and between transactions and messages). Some may not be “solvable” exactly and may require user interface design to hide the nuances and make the user experience consistent regardless of the underlying distinctions.

In the next sections we will look at various proposals that attempt to address some of these issues.

ERC223: A Proposed Token Contract Interface Standard

The ERC223 proposal attempts to solve the problem of inadvertent transfer of tokens to a contract (that may or may not support tokens) by detecting whether the destination address is a contract or not. ERC223 requires that contracts designed to accept tokens implement a function named tokenFallback. If the destination of a transfer is a contract and the contract does not have support for tokens (i.e., does not implement tokenFallback), the transfer fails.

To detect whether the destination address is a contract, the ERC223 reference implementation uses a small segment of inline bytecode in a rather creative way:

  1. function isContract(address _addr) private view returns (bool is_contract) {
  2. uint length;
  3. assembly {
  4. // retrieve the size of the code on target address; this needs assembly
  5. length := extcodesize(_addr)
  6. }
  7. return (length>0);
  8. }

The ERC223 contract interface specification is:

  1. interface ERC223Token {
  2. uint public totalSupply;
  3. function balanceOf(address who) public view returns (uint);
  4. function name() public view returns (string _name);
  5. function symbol() public view returns (string _symbol);
  6. function decimals() public view returns (uint8 _decimals);
  7. function totalSupply() public view returns (uint256 _supply);
  8. function transfer(address to, uint value) public returns (bool ok);
  9. function transfer(address to, uint value, bytes data) public returns (bool ok);
  10. function transfer(address to, uint value, bytes data, string custom_fallback)
  11. public returns (bool ok);
  12. event Transfer(address indexed from, address indexed to, uint value,
  13. bytes indexed data);
  14. }

ERC223 is not widely implemented, and there is some debate in the ERC discussion thread about backward compatibility and trade-offs between implementing changes at the contract interface level versus the user interface. The debate continues.

ERC777: A Proposed Token Contract Interface Standard

Another proposal for an improved token contract standard is ERC777. This proposal has several goals, including:

  • To offer an ERC20-compatible interface

  • To transfer tokens using a send function, similar to ether transfers

  • To be compatible with ERC820 for token contract registration

  • To allow contracts and addresses to control which tokens they send through a tokensToSend function that is called prior to sending

  • To enable contracts and addresses to be notified of the tokens’ receipt by calling a tokensReceived function in the recipient, and to reduce the probability of tokens being locked into contracts by requiring contracts to provide a tokensReceived function

  • To allow existing contracts to use proxy contracts for the tokensToSend and tokensReceived functions

  • To operate in the same way whether sending to a contract or an EOA

  • To provide specific events for the minting and burning of tokens

  • To enable operators (trusted third parties, intended to be verified contracts) to move tokens on behalf of a token holder

  • To provide metadata on token transfer transactions in userData and operatorData fields

The ongoing discussion on ERC777 can be found on GitHub.

The ERC777 contract interface specification is:

  1. interface ERC777Token {
  2. function name() public constant returns (string);
  3. function symbol() public constant returns (string);
  4. function totalSupply() public constant returns (uint256);
  5. function granularity() public constant returns (uint256);
  6. function balanceOf(address owner) public constant returns (uint256);
  7. function send(address to, uint256 amount, bytes userData) public;
  8. function authorizeOperator(address operator) public;
  9. function revokeOperator(address operator) public;
  10. function isOperatorFor(address operator, address tokenHolder)
  11. public constant returns (bool);
  12. function operatorSend(address from, address to, uint256 amount,
  13. bytes userData,bytes operatorData) public;
  14. event Sent(address indexed operator, address indexed from,
  15. address indexed to, uint256 amount, bytes userData,
  16. bytes operatorData);
  17. event Minted(address indexed operator, address indexed to,
  18. uint256 amount, bytes operatorData);
  19. event Burned(address indexed operator, address indexed from,
  20. uint256 amount, bytes userData, bytes operatorData);
  21. event AuthorizedOperator(address indexed operator,
  22. address indexed tokenHolder);
  23. event RevokedOperator(address indexed operator, address indexed tokenHolder);
  24. }
ERC777 hooks

The ERC777 tokens sender hook specification is:

  1. interface ERC777TokensSender {
  2. function tokensToSend(address operator, address from, address to,
  3. uint value, bytes userData, bytes operatorData) public;
  4. }

The implementation of this interface is required for any address wishing to be notified of, to handle, or to prevent the debit of tokens. The address for which the contract implements this interface must be registered via ERC820, whether the contract implements the interface for itself or for another address.

The ERC777 tokens recipient hook specification is:

  1. interface ERC777TokensRecipient {
  2. function tokensReceived(
  3. address operator, address from, address to,
  4. uint amount, bytes userData, bytes operatorData
  5. ) public;
  6. }

The implementation of this interface is required for any address wishing to be notified of, to handle, or to reject the reception of tokens. The same logic and requirements apply to the tokens recipient as to the tokens sender interface, with the added constraint that recipient contracts must implement this interface to prevent locking tokens. If the recipient contract does not register an address implementing this interface, the transfer of tokens will fail.

An important aspect is that only one token sender and one token recipient can be registered per address. Hence, for every ERC777 token transfer the same hook functions are called upon debit and reception of every ERC777 token transfer. A specific token can be identified in these functions using the message’s sender, which is the specific token contract address, to handle a particular use case.

On the other hand, the same token sender and token recipient hooks can be registered for multiple addresses and the hooks can distinguish who are the sender and the intended recipient using the from and to parameters.

A reference implementation of ERC777 is linked in the proposal. ERC777 depends on a parallel proposal for a registry contract, specified in ERC820. Some of the debate on ERC777 is about the complexity of adopting two big changes at once: a new token standard and a registry standard. The discussion continues.

ERC721: Non-fungible Token (Deed) Standard

All the token standards we have looked at so far are for fungible tokens, meaning that units of a token are interchangeable. The ERC20 token standard only tracks the final balance of each account and does not (explicitly) track the provenance of any token.

The ERC721 proposal is for a standard for non-fungible tokens, also known as deeds.

From the Oxford Dictionary:

deed: A legal document that is signed and delivered, especially one regarding the ownership of property or legal rights.

The use of the word “deed” is intended to reflect the “ownership of property” part, even though these are not recognized as “legal documents” in any jurisdiction—yet. It is likely that at some point in the future, legal ownership based on digital signatures on a blockchain platform will be legally recognized.

Non-fungible tokens track ownership of a unique thing. The thing owned can be a digital item, such as an in-game item or digital collectible; or the thing can be a physical item whose ownership is tracked by a token, such as a house, a car, or an artwork. Deeds can also represent things with negative value, such as loans (debt), liens, easements, etc. The ERC721 standard places no limitation or expectation on the nature of the thing whose ownership is tracked by a deed and requires only that it can be uniquely identified, which in the case of this standard is achieved by a 256-bit identifier.

The details of the standard and discussion are tracked in two different GitHub locations:

To grasp the basic difference between ERC20 and ERC721, it is sufficient to look at the internal data structure used in ERC721:

  1. // Mapping from deed ID to owner
  2. mapping (uint256 => address) private deedOwner;

Whereas ERC20 tracks the balances that belong to each owner, with the owner being the primary key of the mapping, ERC721 tracks each deed ID and who owns it, with the deed ID being the primary key of the mapping. From this basic difference flow all the properties of a non-fungible token.

The ERC721 contract interface specification is:

  1. interface ERC721 /* is ERC165 */ {
  2. event Transfer(address indexed _from, address indexed _to, uint256 _deedId);
  3. event Approval(address indexed _owner, address indexed _approved,
  4. uint256 _deedId);
  5. event ApprovalForAll(address indexed _owner, address indexed _operator,
  6. bool _approved);
  7. function balanceOf(address _owner) external view returns (uint256 _balance);
  8. function ownerOf(uint256 _deedId) external view returns (address _owner);
  9. function transfer(address _to, uint256 _deedId) external payable;
  10. function transferFrom(address _from, address _to, uint256 _deedId)
  11. external payable;
  12. function approve(address _approved, uint256 _deedId) external payable;
  13. function setApprovalForAll(address _operateor, boolean _approved) payable;
  14. function supportsInterface(bytes4 interfaceID) external view returns (bool);
  15. }

ERC721 also supports two optional interfaces, one for metadata and one for enumeration of deeds and owners.

The ERC721 optional interface for metadata is:

  1. interface ERC721Metadata /* is ERC721 */ {
  2. function name() external pure returns (string _name);
  3. function symbol() external pure returns (string _symbol);
  4. function deedUri(uint256 _deedId) external view returns (string _deedUri);
  5. }

The ERC721 optional interface for enumeration is:

  1. interface ERC721Enumerable /* is ERC721 */ {
  2. function totalSupply() external view returns (uint256 _count);
  3. function deedByIndex(uint256 _index) external view returns (uint256 _deedId);
  4. function countOfOwners() external view returns (uint256 _count);
  5. function ownerByIndex(uint256 _index) external view returns (address _owner);
  6. function deedOfOwnerByIndex(address _owner, uint256 _index) external view
  7. returns (uint256 _deedId);
  8. }