Skip to content

Latest commit

 

History

History
1045 lines (711 loc) · 79.1 KB

10tokens.asciidoc

File metadata and controls

1045 lines (711 loc) · 79.1 KB

Tokens

What are tokens?

The word token derives from the Old English "tacen" meaning a sign or symbol. Commonly used to mean privately-issued coin-like items that have insignificant value, as used in transportation tokens, laundry tokens, arcade tokens.

Nowadays, tokens based on blockchains are redefining the word to mean blockchain-based abstractions that can be owned and that represent assets, currency, or access rights.

The association between the word "token" and insignificant value has a lot to do with the limited use of physical tokens. Often restricted to specific businesses, organizations or locations, physical tokens are not easily exchangeable and cannot be used for more than one function. With blockchain tokens, these restrictions are erased. Many of these tokens serve multiple purposes, globally and can be traded for each other or for other currencies in global liquid markets. With those restrictions gone, the "insignificant value" expectation is also a thing of the past.

In this section we look at various uses for tokens and how they are created. We also discuss attributes of tokens such as fungibility and intrinsicality. Finally, we examine the standards and technologies that they are based on and experiment by building our own tokens.

How are tokens used?

The most obvious use of tokens is as digital private currencies. However, this is only one possible use. Tokens can be programmed to serve many different functions, often overlapping. For example, a token can simultaneously convey a voting right, an access right and ownership of a resource. Currency is just the first "app".

Currency

A token can serve as a form of currency, with a value determined through private trade. For example, ether or bitcoin.

Resource

A token can represent a resource earned or produced in a sharing-economy or resource-sharing environment. For example, a storage or CPU token representing resources that can be shared over a network.

Asset

A token can represent ownership of an intrinsic or extrinsic, tangible or intangible asset. For example, gold, real-estate, a car, oil, energy etc.

Access

A token can represent access rights and even convey access to a digital or physical property, such as a discussion forum, an exclusive website, a hotel room, a rental car.

Equity

A token can represent shareholder equity in a digital organization (e.g. a DAO) or legal fiction (e.g. a corporation)

Voting

A token can represent voting rights in a digital or legal system.

Collectible

A token can represent a digital (e.g. CryptoPunks) or physical collectible (e.g. a painting)

Identity

A token can represent a digital (e.g. avatar) or legal identity (e.g. national ID).

Attestation

A token can represent a certification or attestation of fact by some authority or by a decentralized reputation system.

Often, a single token has several of these functions overlapping. In some cases it is hard to discern between them, as the physical equivalents have always been inextricably linked. For example, in the physical world, a driver’s license (attestation) is also an identity document (identity) and the two cannot be separated. In the digital realm, previously commingled functions can be separated and developed independently (e.g. an anonymous attestation).

Tokens and fungibility

From Wikipedia:

In economics, fungibility is the property of a good or a commodity whose individual units are essentially interchangeable.

Tokens are fungible when we can substitute any single unit of the token for another without any difference in its value or function. For example, ether is a fungible token, as any unit of ether has the same value and use as any other unit of ether.

Strictly speaking, if a token’s historical provenance can be tracked, then it is not entirely fungible. The ability to track provenance can lead to blacklisting and whitelisting, reducing or eliminating fungibility. We will examine this further in [privacy].

Non-fungible tokens are tokens that each represent a unique tangible or intangible item and therefore are not interchangeable. For example, a token that represents ownership of a specific Van Gogh painting is not equivalent to another token that represents a Picasso. Similarly, a token representing a specific digital collectible such as a specific CryptoKitty (see [cryptoKitties]) is not interchangeable with any other CryptoKitty.

We will see examples of both fungible and non-fungible tokens later in this section.

Counterparty risk

Counterparty risk is the risk that the other party in a transaction will fail to meet their obligations. Some types of transactions create additional counterparty risks because of the addition of more than two parties in the transaction. For example, if you hold a certificate of deposit for a precious metal and you sell that to someone, there are at least 3 parties in that transaction: the seller, the buyer and the custodian of the precious metal. Someone holds the physical asset and by necessity they become a party to, and add counterparty risk, to any transaction involving that asset. When any asset is traded indirectly through the exchange of a token of ownership, there is additional counterparty risk from the custodian of the asset. Do they have the asset? Will they recognize (or allow) the transfer of ownership based on the transfer of a token (such as a certificate, deed, title or digital token). In the world of digital tokens, it is important to understand who holds the asset that is represented by the token and what rules apply to that underlying asset.

Tokens and intrinsicality

The word "intrinsic" derives from the Latin "intra", meaning "from within".

Some tokens represent digital items that are intrinsic to the blockchain. Those digital assets are governed by consensus rules, just like the tokens themselves. This has an important implication: tokens that represent intrinsic assets do not carry additional counterparty risk. If you hold the keys to 1 ether, there is no other party holding that ether for you. The blockchain consensus rules apply and your ownership (control) of the private keys is equivalent to ownership of the asset, without any intermediary.

Conversely, many tokens are used to represent extrinsic things, like real-estate, corporate voting shares, trademarks, gold bars. The ownership of these items, which are not "within" the blockchain are governed by law, custom and policy that are separate from the consensus rules that govern the token. As a result, these extrinsic assets carry additional counterparty risk because they are held by custodians, recorded in external registries, or controlled by laws and policies outside the blockchain environment.

One of the most important ramifications of blockchain-based tokens is the ability to replace extrinsic assets into intrinsic assets and thereby remove counterparty risk. A good example is moving from equity in a corporation (extrinsic) to a equity or voting token in a decentralized autonomous organization or similar (intrinsic) organization.

Using tokens: utility or equity

Almost all projects in Ethereum today are launching with some kind of token. But do all these projects really need a token? Are there any disadvantages to using a token, or will we see the slogan "tokenize all the things" come to fruition?

First, let’s start by clarifying the role of a token in a new project. The majority of projects are using tokens in one of two ways: either as "utility tokens" or as "equity tokens". Very often, those two roles are conflated and difficult to distinguish.

Utility tokens are those where use of the token is required to pay for use of a service, application or resource. Examples of utility tokens include tokens that represent resources such as shared storage, access to services such as social media networks, or ether itself as gas for the Ethereum platform. By comparison, equity tokens are those that represent shares in a startup.

Equity tokens can be as limited as non-voting shares for distribution of dividends and profits, or as expansive as voting shares in a decentralized-autonomous-organization, where management of the platform is through majority votes by the token holders.

It’s not a duck

Just because a token is used to fundraise for a startup, doesn’t mean it has to be used as payment for the service, and vice-versa. Many startups, however face a difficult problem: tokens are a great fundraising mechanism, but offering securities (equity) to the public is a regulated activity in most jurisdictions. By disguising equity tokens as utility tokens, many startups hope to get around these regulatory restrictions and raise money from a public offering while presenting it as a pre-sale of a utility token. Whether these thinly disguised equity offerings will be able to skirt the regulators remains to be seen.

As the popular saying goes: "If it walks like a duck and quacks like a duck - it’s a duck". Regulators are not likely to be distracted by these semantic contortions, quite the opposite, they are more likely to see such legal sophistry as an attempt to deceive the public.

Utility tokens: who needs them?

The real problem however is that utility tokens introduce significant risks and adoption barriers for startups. Perhaps in a distant future "tokenize all the things" becomes a reality. But, at present, the number of people who have access to, understanding, and desire to use a token is a subset of the already small cryptocurrency market.

For a startup, each innovation represents a risk and a market filter. Innovation is taking the road least traveled, walking away from the path of tradition. It is already a lonely walk. If a startup is trying to innovate in a new area of technology, such as storage sharing over P2P networks, that is a lonely enough path. Adding a utility token to that innovation and requiring users to adopt tokens in order to use the service compounds the risk and increases the barriers to adoption. It’s walking off the already lonely trail of P2P storage innovation and into the wilderness.

Think of each innovation as a filter. It limits adoption to the subset of the market that can become early adopters of this innovation. Adding a second filter compounds that effect, further limiting the addressable market. You are asking your early adopters to adopt not one but two completely new technologies: the novel application/platform/service you built, and the token economy.

For a startup, each innovation introduces risks that increase the chance of failure of the startup. If you take your already risky startup idea and add a utility token, you are adding all the risks of the underlying platform (Ethereum), broader economy (exchanges, liquidity), regulatory environment (equity/commodity regulators) and technology (smart contracts, token standards). That’s a lot of risk for a startup.

Advocates of "tokenize all the things" will likely counter that by adopting tokens, they are also inheriting the market enthusiasm, early adopters, technology, innovation and liquidity of the entire token economy. That is true too. The question is whether the benefits and enthusiasm outweigh the risks and uncertainties.

Finally, at the beginning of this chapter, when introducing tokens we discuss the colloquial meaning of token as "something of insignificant value". The underlying reason for the insignificant value of most tokens is because they can only be used in a very narrow context: one bus company, one laundromat, one arcade, one hotel, one company store. Limited liquidity, limited applicability and high conversion costs reduce the value of tokens, all the way down until it is only a "token" value. So when you add a utility token to your platform, but the token can only be used on your own one platform with a small market, you are re-creating the conditions that made physical tokens worthless. If in order to use your platform, a user has to convert something into your utility token, use it and then convert the remainder back into something more generally useful, you’ve created company scrip. The switching costs of a digital token are orders of magnitude lower than a physical token without a market. But the switching costs are not zero. Utility tokens that work across an entire industry sector will be very interesting and probably quite valuable. But if you set up your startup to have to bootstrap an entire industry standard in order to succeed, you may have already failed.

Make this decision for the right reasons. Adopt a token because your application cannot work without a token (e.g. Ethereum). Adopt it because the token solves a fundamental market barrier or access problem. Don’t introduce a utility token because it is the only way you can raise money fast and you need to pretend it’s not a public securities offering.

Token Standards

Blockchain tokens have 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.

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 ERC20. The ERC20 request for comments, eventually became Ethereum Improvement Proposal EIP20, but is mostly still referred to by the original name ERC20. You can read the standard here:

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 & events
totalSupply

Returns the total units of this token that currently exist. ERC20 tokens can have fixed or 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 below.

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

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

Approval event

Event logged upon successful call to approve.

ERC20 optional functions
name

Returns a 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:

contract ERC20 {
   function totalSupply() constant returns (uint theTotalSupply);
   function balanceOf(address _owner) constant returns (uint balance);
   function transfer(address _to, uint _value) returns (bool success);
   function transferFrom(address _from, address _to, uint _value) returns (bool success);
   function approve(address _spender, uint _value) returns (bool success);
   function allowance(address _owner, address _spender) constant returns (uint remaining);
   event Transfer(address indexed _from, address indexed _to, uint _value);
   event Approval(address indexed _owner, address indexed _spender, uint _value);
}
ERC20 data structures

If you examine any ERC20 implementation, it will contain 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.

Balances: a mapping from address (owner) to amount (balance)
mapping(address => uint256) balances;

The second data structure is a data mapping of allowances. As we will see in ERC20 workflows: "transfer" and "approve & transferFrom", 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:

Allowances: a mapping from address (owner) to address (spender) to amount (allowance)
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 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 an 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 balance to each buyer of the token.

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 AliceCoin, calling approve, with the address of AliceICO and 50% of the totalSupply. This will trigger the Approval event. Now, the AliceICO contract can sell AliceCoin. When AliceICO receives ether from Bob, it needs to send some AliceCoin to Bob in return. To do that, AliceICO calls the AliceCoin transferFrom function, with Alice’s address as the sender, Bob’s address as the recipient and the amount of tokens to give Bob. 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 thirty lines of Solidity code, most implementations are more complex, to account for potential security vulnerabilities. There are two implementations mentioned in the EIP20 standard:

Consensys EIP20

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

You can read the Solidity code for Consensys' implementation here: https://github.com/ConsenSys/Tokens/blob/master/contracts/eip20/EIP20.sol

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 (see [truffle]). The example assumes you have already installed truffle, configured it, and are familiar with its basic operation.

We will call our token "Mastering Ethereum Token", with symbol "MET".

You can find this example in the book’s GitHub repository: https://github.com/ethereumbook/ethereumbook/blob/first_edition/code/METoken

First, let’s create and initialize a truffle project directory, the same way we did in [truffle_project_directory]. Run these four commands and accept the default answers to any questions:

$ mkdir METoken
$ cd METoken
METoken $ truffle init
METoken $ npm init

You should now have the following directory structure:

METoken/
├── contracts
│   └── Migrations.sol
├── migrations
│   └── 1_initial_migration.js
├── package.json
├── test
├── truffle-config.js
└── truffle.js

Edit the truffle.js configuration file to setup your truffle environment, or copy the one we used from:

If you use the example truffle.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 ganache or Kovan. You can export your test network private key from MetaMask.

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 StandardContract, which implements some important security checks and is easy to extend. Let’s import that library:

$ npm install zeppelin-solidity

+ [email protected]
added 8 packages in 2.504s

The zeppelin-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 is very simple, as it inherits all the functionality from the OpenZeppelin StandardToken library:

METoken.sol : A Solidity contract implementing an ERC20 token
link:code/METoken/contracts/METoken.sol[role=include]

Here, we are defining the optional variables name, symbol, and decimals. We also define an _initial_supply variable, set to 21 million tokens, and two decimals of subdivision (2.1 billion total). 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:

$ truffle compile
Compiling ./contracts/METoken.sol...
Compiling ./contracts/Migrations.sol...
Compiling zeppelin-solidity/contracts/math/SafeMath.sol...
Compiling zeppelin-solidity/contracts/token/ERC20/BasicToken.sol...
Compiling zeppelin-solidity/contracts/token/ERC20/ERC20.sol...
Compiling zeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol...
Compiling zeppelin-solidity/contracts/token/ERC20/StandardToken.sol...

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

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

Here’s what it contains:

2_deploy_contracts: Migration to deploy METoken
link:code/METoken/migrations/2_deploy_contracts.js[role=include]

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, as we did in [using_ganache].

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

$ truffle migrate --network ganache
Using network 'ganache'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0xb2e90a056dc6ad8e654683921fc613c796a03b89df6760ec1db1084ea4a084eb
  Migrations: 0x8cdaf0cd259887258bc13a92c0a6da92698644c0
Saving successful migration to network...
  ... 0xd7bc86d31bee32fa3988f1c1eabce403a1b5d570340a3a9cdba53a472ee8c956
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying METoken...
  ... 0xbe9290d59678b412e60ed6aefedb17364f4ad2977cfb2076b9b8ad415c5dc9f0
  METoken: 0x345ca3e014aaf5dca488057592ee47305d9b3e10
Saving successful migration to network...
  ... 0xf36163615f41ef7ed8f4a8f192149a0bf633fe1a2398ce001bf44c43dc7bdda0
Saving artifacts...

On the ganache console, we should see that our deployment has created 4 new transactions:

METoken deployment on Ganache
Figure 2. METoken deployment on Ganache

====使用truffle console与METoken进行交互

我们可以使用truffle console与ganache区块链的合约进行交互。这是一个交互式的JavaScript环境,它提供了对truffle环境的访问,并通过Web3向区块链提供了访问。在这种情况下,我们将truffle console连接到ganache区块链:

$ truffle console --network ganache
truffle(ganache)>

truffle(ganache)>表示我们已经连接到ganache区块链,并准备好输入命令。truffle console支持所有的truffle命令,因此我们可以从console中进行编译(compile)和迁移(migrate)。我们已经运行了这些命令,直接进入合约。在truffle环境中,METoken合约作为一个JavaScript对象存在。在提示符处输入METoken,它将转储整个合约的定义:

truffle(ganache)> METoken
{ [Function: TruffleContract]
  _static_methods:

[...]

currentProvider:
 HttpProvider {
   host: 'http://localhost:7545',
   timeout: 0,
   user: undefined,
   password: undefined,
   headers: undefined,
   send: [Function],
   sendAsync: [Function],
   _alreadyWrapped: true },
network_id: '5777' }

METoken还公开了几个属性,例如合约的地址(由转移命令(migrate)部署):

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

如果我们想要与已部署的合约进行交互,必须用JavaScript语言“promise”的形式进行异步调用。 我们用deployed函数来获取合约实例,然后调用totalSupply函数:

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

接下来,让我们使用由ganache创建的账户来检查我们的METoken余额并将一些METoken发送到另一个地址。 首先,让我们获取帐户地址:

truffle(ganache)> let accounts
undefined
truffle(ganache)> web3.eth.getAccounts((err,res) => { accounts = res })
undefined
truffle(ganache)> accounts[0]
'0x627306090abab3a6e1400e9345bc60c78a8bef57'

帐户列表现在包含由ganache创建的所有帐户,以及账户[0]是部署了该METoken合约的帐户。 它应该有一个METoken的balance,因为METoken构造函数将整个令牌提供给创建它的地址。 让我们核对:

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

最后,让我们调用合约的传递函数,将1000.00 METoken从帐户[0]转移到帐户[1]:

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

undefined
truffle(ganache)> METoken.deployed().then(instance => { instance.balanceOf(accounts[1]).then(console.log) })
undefined
truffle(ganache)> BigNumber { s: 1, e: 5, c: [ 100000 ] }
Tip

METoken具有2位精度的小数,这意味着1个METoken在合约中是100个单位。 当我们转移1000个METoken时,我们在传递函数中将该值指定为100,000。

正如你所见,在console中,帐户[0]现在拥有20,999,000 MET,帐户[1]拥有1000 MET。

如果你切换到ganache图形用户界面,您将看到称为传递函数(transfer function)的记录:

METoken在Ganache中的传递
Figure 3. METoken transfer on Ganache

====发送ERC20代币到合约地址

到目前为止,我们已经设置了ERC20令牌并从一个帐户转移到另一个帐户。 我们用于这些示范的所有账户都是外部拥有账户(EOAs),这意味着它们由私钥控制,而不是合约。 如果我们将MET发送到合约地址会发生什么? 让我们一起发现!

首先,我们将其他合约部署到我们的测试环境中。 对于这个例子,我们将使用我们的第一份合约Faucet.sol。 我们将它添加到METoken项目中,方法是将其复制到合约目录中。 我们的目录应该是这样的:

METoken/
├── contracts
│   ├── Faucet.sol
│   ├── METoken.sol
│   └── Migrations.sol

我们也添加了一个migration, 以便将Faucet和METoken分离:

var Faucet = artifacts.require("Faucet");

module.exports = function(deployer) {
  // Deploy the Faucet contract as our only task
  deployer.deploy(Faucet);
};

让我们在truffle console中编译(compile)和传递(migrate)合约。

$ truffle console --network ganache
truffle(ganache)> compile
Compiling ./contracts/Faucet.sol...
Writing artifacts to ./build/contracts

truffle(ganache)> migrate
Using network 'ganache'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0x89f6a7bd2a596829c60a483ec99665c7af71e68c77a417fab503c394fcd7a0c9
  Migrations: 0xa1ccce36fb823810e729dce293b75f40fb6ea9c9
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Replacing METoken...
  ... 0x28d0da26f48765f67e133e99dd275fac6a25fdfec6594060fd1a0e09a99b44ba
  METoken: 0x7d6bf9d5914d37bcba9d46df7107e71c59f3791f
Saving artifacts...
Running migration: 3_deploy_faucet.js
  Deploying Faucet...
  ... 0x6fbf283bcc97d7c52d92fd91f6ac02d565f5fded483a6a0f824f66edc6fa90c3
  Faucet: 0xb18a42e9468f7f1342fa3c329ec339f254bc7524
Saving artifacts...

太棒了!现在让我们发送一些MET到Faucet合约吧:

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

好了,我们已经把1000个MET转移到Faucet合约上了。现在,我们怎么从Faucet上提取这些MET呢?

请牢记,Faucet.sol是一个非常简单的合约。它只有一个功能,那就是提取以太(ether)。它并没有一个提取MET或者其他任何ERC20 代币的功能。如果我们让它行使提取(非以太)的动作,它会尝试发送以太,但由于faucet不会达到以太的平衡,这个动作将会失败。 METoken合约确知Faucet有余额,但它唯一能转移余额的方式是接收到来自合约地址的转账命令。 不管怎样,我们需要让Faucet合约调用METoken的传递函数。

如果你想知道下一步该做什么的话,就别想了。 这个问题没有解决办法。 发送到Faucet的MET卡住了,永久的。 只有Faucet合约才能转移它,Faucet合约没有代码来调用ERC20代币合约的传递函数。

也许你预料到了这个问题, 但很大可能是你并没有。 实际上,数百名以太坊用户也无意将各种代币转移到没有任何ERC20能力的合约上。 据估计,价值超过250万美元的代币像这样被“卡住”,并且永远丢失。

ERC20代币的用户在转移代币时无意中丢失代币的方式之一是他们尝试转移到具有交易或其他服务的地方。 他们从交易所网站复制了以太坊地址,认为只需发送代币即可。 但是,许多交易所实际上公布的都是合约的收货地址! 这些合约具有许多不同的功能,通常将所有发送给他们的资金都清扫到“冷库”或其他集中的钱包。 尽管有许多警告说“不要将代币发送到这个地址”,但依然有许多代币以这种方式丢失。

演示批准(approve)和转让(transferForm)的工作流程

我们的Faucet合约无法处理ERC20代币,使用传递函数向它发送代币会导致这些代币丢失。 我们重写合约,并让它能处理ERC20代币。 具体而言,我们将把它变成一个可以把MET发给任何访问者的faucet。

在这个例子中,我们制作了一个truffle程序目录的副本,称之为METoken_METFaucet,初始化 truffle,npm,安装OpenZeppelin相关项并复制METoken.sol合约。 请参阅我们的第一个示例 Launching our own ERC20 token,已有详细说明。

现在,让我们创建一个新的faucet合约,称之为METFaucet.sol.看起来像这样:METFaucet.sol: 一份为了METoken的faucet合约。

METFaucet.sol: a faucet for METoken
include::code/METoken_METFaucet/contracts/METFaucet.sol

我们对基本的faucet样本做了一些改变。 由于METFaucet将使用METoken中的transferFrom函数,因此它需要两个额外的变量。 一个将保存已部署METoken合约的地址。 另一个将保存允许faucet提款的MET所有者的地址。 METFaucet将调用METoken.transferFrom并指示它将MET从所有者处移至faucet中收到提取请求的地址。

我们在这里声明这两个变量:

StandardToken public METoken;
address public METOwner;

由于faucet需要使用METoken和METOwner的正确地址进行初始化,因此我们需要声明一个自定义构造函数:

// METFaucet constructor, provide the address of METoken contract and
// the owner address we will be approved to transferFrom
function METFaucet(address _METoken, address _METOwner) public {

	// Initialize the METoken from the address provided
	METoken = StandardToken(_METoken);
	METOwner = _METOwner;
}

下一个改变是提取功能。 METFaucet使用METoken中的transferFrom函数而不是调用传递函数,并要求METoken将MET传递给faucet接收人:

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

最后,由于faucet不再发送以太,所以我们应该可以防止任何人将以太发送到METFaucet,因为我们不希望它卡住。 我们更改回退应付功能以拒绝传入的以太网,并使用回复功能来还原所有收款:

// REJECT any incoming ether
function () public payable { revert(); }

现在METFaucet.sol代码已准备就绪,我们需要修改迁移脚本来部署它。 这个迁移脚本会更复杂一点,因为METFaucet依赖于METoken的地址。 我们将使用JavaScript承诺按顺序部署这两个合约。 创建2_deply_contracts.js,如下所示:

var METoken = artifacts.require("METoken");
var METFaucet = artifacts.require("METFaucet");
var owner = web3.eth.accounts[0];

module.exports = function(deployer) {

	// Deploy the METoken contract first
	deployer.deploy(METoken, {from: owner}).then(function() {
		// then deploy METFaucet and pass the address of METoken
		// and the address of the owner of all the MET who will approve METFaucet
		return deployer.deploy(METFaucet, METoken.address, owner);
  	});
}

现在,我们可以测试truffle console中的所有内容。 首先,使用迁移函数来部署合约。 当METoken被部署时,它会将所有MET分配给创建它的帐户,web3.eth.accounts [0]。 然后,在METoken中调用批准函数来批准METFaucet发送高达1000 的MET,用web3.eth.accounts [0]表示。 最后,为了测试faucet,我们调用web3.eth.accounts [1]中的METFaucet.withdraw并尝试提取10 MET。 以下是控制台命令:

$ truffle console --network ganache
truffle(ganache)> migrate
Using network 'ganache'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0x79352b43e18cc46b023a779e9a0d16b30f127bfa40266c02f9871d63c26542c7
  Migrations: 0xaa588d3737b611bafd7bd713445b314bd453a5c8
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Replacing METoken...
  ... 0xc42a57f22cddf95f6f8c19d794c8af3b2491f568b38b96fef15b13b6e8bfff21
  METoken: 0xf204a4ef082f5c04bb89f7d5e6568b796096735a
  Replacing METFaucet...
  ... 0xd9615cae2fa4f1e8a377de87f86162832cf4d31098779e6e00df1ae7f1b7f864
  METFaucet: 0x75c35c980c0d37ef46df04d31a140b65503c0eed
Saving artifacts...
truffle(ganache)> METoken.deployed().then(instance => { instance.approve(METFaucet.address, 100000) })
truffle(ganache)> METoken.deployed().then(instance => { instance.balanceOf(web3.eth.accounts[1]).then(console.log) })
truffle(ganache)> BigNumber { s: 1, e: 0, c: [ 0 ] }
truffle(ganache)> METFaucet.deployed().then(instance => { instance.withdraw(1000, {from:web3.eth.accounts[1]}) } )
truffle(ganache)> METoken.deployed().then(instance => { instance.balanceOf(web3.eth.accounts[1]).then(console.log) })
truffle(ganache)> BigNumber { s: 1, e: 3, c: [ 1000 ] }

正如从结果所见,我们可以使用批准和转让工作流程来授权一个合约转移另一个代币中中定义的代币。 如果使用得当,ERC20代币可以由外部拥有的地址和其他合约使用。

然而这样正确管理ERC20代币的负担会推送到用户界面。 如果用户错误地尝试将ERC20代币转移到合约地址,并且该合约不能接收ERC20代币,则代币将丢失。

Issues with ERC20 tokens

ERC20 代币的问题

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 Initial Coin Offerings (ICOs). However there are some potential pitfalls, as we saw with the issue of transferring tokens to contract addresses.

ERC20 代币标准已经被极其广泛地采用。成千上万的代币发行即是为了实验新的特性也是为了通过众筹拍卖或 ICO 来融资。不过,正如我们看到,采用 ERC20 代币在将代币转移到合约地址时会有一些问题。 (topsun: 即是为了实验新的特性 -→ 即是为了实验新的性能)           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 which 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 recipients 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 the available ERC20 contracts.

其中一个不太明显的问题是 ERC20 代币�和以太(ether)本身是有细微差别的。以太的转移是靠一笔有收款人地址的交易(transaction)来完成,而代币的转移是在某个具体的代币合约里完成,其接收者是这个代币合约而不是一个收款人地址。�代币合约会跟踪余额以及发布事件。在一次代币转移中,实际上没有��一笔真正的交易发到代币的接收者那里,而是把收款人地址加到了代币合约中。一笔通常意义上发送以太到某个地址的交易是在改变那个地址的状态。一笔转移代币到某个地址的交易只更改了合约的状态而没有改变收款人地址的状态。即便是一个支持 ERC20 代币的钱包也不会变得能够知道代币余额,除非用户特地添加了个监控代币余额的合约。有些钱包会监听最热门的合约来获取用户所掌握的代币余额信息,但是这仅限于非常小部分的 ERC20 合约。 (topsun:而是把收款人地址加到了代币合约中 -→而是把收款人地址加到了包含于代币合约中映射)

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, 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 tokens contracts are being watched by the block explorer or wallet you use to view your address.

事实上,一个用户不太可能会想监听所有的代币合约里的代币余额。很多 ERC20 代币更像是垃圾邮件而不是有价值的代币。这些代币会为了吸引用户,自动为有以太活动的账户创建余额。如果你拥有一个有长期活动历史的以太坊地址,特别是如果这个地址是在预售期被创建的,你会发现这里面充满了不知道从哪冒出来的『垃圾』代币。当然,这些地址里不是真正地充满了代币,而是那些代币合约里有你的这些地址。你只有当你用区块链浏览器或者钱包去��监听这些合约里你的地址时才会看到这些余额。 (topsun:你只有当你用区块链浏览器或者钱包去��监听这些合约里你的地址时才会看到这些余额 -→ 只有当你用区块链浏览器或者钱包去��监听这些合约里你的地址时才会看到这些余额 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 subtle distinctions that break that illusion.

代币和�以太的具体表现是不一样的。以太是通过发送函数(send function)进行发送并且可以被任意合约里的应付函数(payable function)或任意外部拥有账户(EOA)所接收。代币是通过只存在鱼 ERC20 合约里的 transfer 或者 approve & transferFrom 函数进行发送,并且至少在 ERC20 中并不会触发任何接收人合约的应付函数。代币的职能本来是应该和以太这样的加密货币一样,但是他们本身和以太一些细微的不同打破了这样的幻想。 (topsun: 1.代币和�以太的具体表现是不一样的        -→代币和�以太的运作方式是不一样的。   2.任意外部拥有账户(EOA) -→任意外部持有账户(EOA) 3.代币是通过只存在鱼 ERC20 合约里的 -→代币是通过只存在于 ERC20 合约里的 ) Consider another issue. To send ether, or use any Ethereum contract you need ether to pay 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 the gas for you. 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. Now try sending the token 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.

现在我们来看另一个问题。想要给别人转以太或者使用以太坊合约,你需要以太来支付相应的交易工作量(gas)。想要给别人转代币,你同样需要以太。你无法用代币来支付一笔交易所需的计算工作量,你的代币合约也同样无法为你支付所需的计算工作量。这就会导致一些非常奇怪的用户体验。打比方说你用某个交易所把一些比特币换成了某种代币。你『接收』到了这些代币并且用钱包监听了这个代币的合约以显示你的余额。这跟你在钱包里拥有的其他加密货币看起没有什么不同。但是如果你现在想要把这些代币转出去,你的钱包就会提醒你说你需要有以太才能转账。你可能会觉得非常奇怪,毕竟你接收这些代币的时候都没有需要用到以太。你可能没有以太。你可能根本就不知道这个代币是基于以太坊 ERC20 的一种代币,你可能还觉得它是一种拥有自己区块链的加密货币。不管怎样,这样的臆想在你转不出去账的时候破灭了。 (topsun:1.打比方说你用某个交易所把一些比特币换成了某种代币 -→ 打比方说你用某个交易所或"Shapeshift"把一些比特币换成了某种代币)

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, 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.

有些问题是针对于 ERC20 代币的,而其他的是更宽泛的、�跟以太坊抽象和接口边界有关的问题。有些问题能通过修改代币接口解决,有的可能需要改变以太坊的基础架构(比如外部拥有账户(EOA)和合约(contract)之间的差异,交易(transaction)和消息(message)之间的差异)。有些差异可能并不能被消除,它们需要通过一些用户界面的隐藏使得用户感知不到内部的细微差异。 (topsun:1.跟以太坊抽象和接口边界有关的问题 -→ 是一些和以太坊本身的抽象和接口边界有关的问题    2.有些差异可能并不能被消除,它们需要通过一些用户界面的隐藏使得用户感知不到内部的细微差异。 -→ 有些问题可能并不会被完美解决,但通过用户界面设计的技巧,这些问题得以隐藏,从而使用户感知不到内部的细微差异。) In the next sections we will look at various proposals that attempt to address some of these issues.

在下面一个章节我们将会介绍一些想要解决以上部分问题的提案。 (topsun:在下面一个章节我们将会介绍一些想要解决以上部分问题的提案。 -→ 在下面一个章节我们将会介绍一些针对解决以上部分问题的提案。) ==== ERC223 - a proposed token contract interface standard

ERC223 - 一个代币合约接口标准提案

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.

ERC223 提案试图解决通过检测一次交易的目标地址是否是合约来解决用户因为粗心而错把代币转到一个合约(此合约可能不支持代币转账)。ERC223 强制接收代币的合约实现一个叫做 tokenFallback 的函数。如果一次交易的目标地址是一个合约,而这个合约并没有支持代币(即没有实现 tokenFallback),那么这次交易就会失败。

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:

为了检测一笔交易的目标地址是否是合约,ERC223 的参考实现采用了一个很聪明的方法:利用小段的内联字节码:

function isContract(address _addr) private view returns (bool is_contract) {
	uint length;
	assembly {
		  //retrieve the size of the code on target address, this needs assembly
		  length := extcodesize(_addr)
	}
	return (length>0);
}

You can see the discussion around the ERC223 proposal here:

关于 ERC223 的讨论可以在这里找到:

The ERC223 contract interface specification is:

ERC223 合约的接口规格:

interface ERC223Token {
  uint public totalSupply;
  function balanceOf(address who) public view returns (uint);

  function name() public view returns (string _name);
  function symbol() public view returns (string _symbol);
  function decimals() public view returns (uint8 _decimals);
  function totalSupply() public view returns (uint256 _supply);

  function transfer(address to, uint value) public returns (bool ok);
  function transfer(address to, uint value, bytes data) public returns (bool ok);
  function transfer(address to, uint value, bytes data, string custom_fallback) public returns (bool ok);

  event Transfer(address indexed from, address indexed to, uint value, bytes indexed data);
}

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

ERC223 并没有被广泛采用。关于 ERC 的讨论里有对 ERC223 向后兼容性以及到底是修改合约接口还是用户界面的争论。这样的争论还在持续。

ERC777 - a proposed token contract interface standard

ERC777 - 一个代币合约接口标准提案

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

ERC777 是另外一个致力于改善代币合约的提案。这个提案有如下几个目标:

  • To offer an ERC20 compatibility interface

  • 提供 ERC20 兼容的接口

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

  • 使用一个类似于以太转账的发送函数来进行代币转账

  • To be compatible with ERC820 for token contract registration

  • 兼容 ERC820 的代币合约注册

  • Contracts and addresses can control which tokens they send through a tokensToSend function that is called prior to sending

  • 在发送代币之前,合约和地址可以控制哪些代币会通过 tokensToSend 函数发送

  • Contracts and addresses are notified by calling a tokensReceived function in the recipient

  • 接收者可以通过调用 tokensReceived 函数来通知合约和地址  (topsun:接收者可以通过调用 tokensReceived 函数来通知合约和地址 -→ 可以通过调用接收方的 tokensReceived 函数来通知合约和地址)

  • Token transfer transactions contain metadata in a userData and operatorData field

  • 代币转账交易会在 userDataoperatorData 中包含元数据

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

  • 转账到合约或外部拥有地址 (EOA) 将会是同样的操作

The details and ongoing discussion on ERC777 can be found here: ethereum/EIPs#777

这是关于 ERC777 的细节以及正在进行的讨论的链接:ethereum/EIPs#777

The ERC777 contract interface specification is:

ERC777 合约的接口规格:

interface ERC777Token {
    function name() public constant returns (string);
    function symbol() public constant returns (string);
    function totalSupply() public constant returns (uint256);
    function granularity() public constant returns (uint256);
    function balanceOf(address owner) public constant returns (uint256);

    function send(address to, uint256 amount) public;
    function send(address to, uint256 amount, bytes userData) public;

    function authorizeOperator(address operator) public;
    function revokeOperator(address operator) public;
    function isOperatorFor(address operator, address tokenHolder) public constant returns (bool);
    function operatorSend(address from, address to, uint256 amount, bytes userData, bytes operatorData) public;

    event Sent(address indexed operator, address indexed from, address indexed to, uint256 amount, bytes userData, bytes operatorData);
    event Minted(address indexed operator, address indexed to, uint256 amount, bytes operatorData);
    event Burned(address indexed operator, address indexed from, uint256 amount, bytes userData, bytes operatorData);
    event AuthorizedOperator(address indexed operator, address indexed tokenHolder);
    event RevokedOperator(address indexed operator, address indexed tokenHolder);
}

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.

ERC777 的提案提供了一份参考实现。ERC777 依赖于 ERC820 所提出的合约注册。因此一些关于 ERC777 的争论围绕着一次性采纳两个大的变化(一个新的代币标准和一个注册标准)的复杂度。这样的争论还在继续。 (topsun: ERC777 依赖于 ERC820 所提出的合约注册 -→ERC777 依赖于 一份在ERC820中提到的注册合约提案) ==== ERC721 - non-fungible token (deed) standard

ERC721 - 不可替换的代币标准(契据)

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

所有我们目前见到过的代币标准都是可替代的代币,�这代表着一个代币里的每一个最小单位都是�可以互相转换的。ERC20 代币标准只追踪每个账户的最终余额,它并不显式地追踪代币从哪里来的。

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

ERC721 提案是关于不可替换代币(又被称为契据)的标准

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, at least not currently.

把契据这个词用在这里是想表达其『财产所有权』的部分,虽然这些契据现在还没有被任何法律认为是有效法律文件。

Non-fungible tokens track ownership of a unique thing. The thing owned can be a digital item, such as a 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, artwork. A deed could 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, only that it can be uniquely identified, which in the case of this standard is achieved by a 256-bit identifier.

不可替换的代币追踪对某个独特事物的所有权。被拥有的事物可以是数字虚拟物品,比如一款游戏或是数字藏品。被拥有的事物也可以是现实世界里的物品,比如一栋房子,一辆车或是一件艺术品。一个契据也可以用来代表负向的拥有权比如借款,抵押,地役权等。ERC721 标准并没有对契据可以追踪所有权的事物做任何限制。ERC721通过256位的标识符来实现了被追踪的事物的唯一标识。 (topsun: ERC721 标准并没有对契据可以追踪所有权的事物做任何限制 -→ERC721 标准并没有对契据可以追踪所有权的事物做任何限制和期望) The details of the standard and discussion are tracked in two different GitHub locations:

此标准的细节和相关讨论记录在下面两个 GitHub 地址里:

Initial proposal: ethereum/EIPs#721

初版提案: ethereum/EIPs#721

Continued discussion: ethereum/EIPs#841

持续讨论: ethereum/EIPs#841

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

�通过查看 ERC721 的内部数据结构,我们可以较好地体会 ERC20 和 ERC721 的区别:

// Mapping from deed ID to owner
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.

ERC20 通过把拥有者作为映射的主键来实现追踪拥有者的余额,而 ERC721 通过把契据 ID 作为映射的主键来实现追踪每个契据的它的拥有者。这个基本的区别影响着不可替换代币的所有属性。 (topsun:而 ERC721 通过把契据 ID 作为映射的主键来实现追踪每个契据的它的拥有者 -→ 而 ERC721 通过把契据 ID 作为映射的主键来实现追踪每个契据和它的拥有者) The ERC721 contract interface specification is:

ERC721 合约的接口规格:

interface ERC721 /* is ERC165 */ {
    event Transfer(address indexed _from, address indexed _to, uint256 _deedId);
    event Approval(address indexed _owner, address indexed _approved, uint256 _deedId);
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

    function balanceOf(address _owner) external view returns (uint256 _balance);
    function ownerOf(uint256 _deedId) external view returns (address _owner);
    function transfer(address _to, uint256 _deedId) external payable;
    function transferFrom(address _from, address _to, uint256 _deedId) external payable;
    function approve(address _approved, uint256 _deedId) external payable;
    function setApprovalForAll(address _operateor, boolean _approved) payable;
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

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

ERC721 同时也支持两个可选的接口,一个关于元数据,另一个则是为了遍历契据和其拥有者。

The ERC721 optional interface for metadata is:

ERC721 可选的元数据接口标准:

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

The ERC721 optional interface for enumeration is:

ERC721 可选的遍历接口是:

interface ERC721Enumerable /* is ERC721 */ {
    function totalSupply() external view returns (uint256 _count);
    function deedByIndex(uint256 _index) external view returns (uint256 _deedId);
    function countOfOwners() external view returns (uint256 _count);
    function ownerByIndex(uint256 _index) external view returns (address _owner);
    function deedOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256 _deedId);
}

Token standards

代币标准

In this section we’ve reviewed several proposed standards, and a couple of widely-deployed standards for token contracts. What exactly do these standards do? Should you use these standards? How should you use them? Should you add functionality beyond these standards? Which standards should you use? We will examine all those questions next.

在这一章里,我们介绍了几个标准提案以及一些已经大规模采纳的代币合约标准。这些标准到底是做什么的?你应该使用这些标准吗?你应该添加标准之外的功能吗?你到底应该使用哪个标准?我们接下来会一一回答这些问题。 (topsun:你应该使用这些标准吗?你应该添加标准之外的功能吗? -→ 你应该使用这些标准吗?怎样使用这些标准?你应该添加标准之外的功能吗?)

What are token standards? What is their purpose?

什么是代币标准?它们的目的是什么?

Token standards are a minimum specification for an implementation. What that means is that in order to be compliant with, say ERC20, you need to, at-minimum, implement the functions and behavior specified by ERC20. You are also free to add to the functionality by implementing functions that are not part of the standard.

代币标准是一套最少需要具体实现的功能。这代表着想要遵循,比如 ERC20 标准,你至少需要实现那些 ERC20 所规定的函数和行为。你同时也可以自由地添加并不在标准里规定的功能。 (topsun:1.代币标准是一套最少需要具体实现的功能。 -→ 代币标准是一系列执行的最简化说明。 2.这代表着想要遵循 -→ 这意味着为了合规)

The primary purpose of these standards is to encourage interoperability between contracts. Thus, all wallets, exchanges, user interfaces and other infrastructure components can interface in a predictable manner with any contract that follows the specification.

这些标准的主要目标是增加合约之间的协作能力。这样的话,所有的钱包、交易所、用户界面和其他的基础设施可以使用可预测的接口来和任何符合标准的合约交流。 (topsun:可以使用可预测的接口来和任何符合标准的合约交流 -→ 可以使用一种可预测的方式来和任何符合标准的合约交流)

The standards are meant to be descriptive, rather than prescriptive. How you choose to implement those functions is up to you - the internal function of the contract is not relevant to the standard. They have some functional requirements, which govern the behavior under specific circumstances, but they do not prescribe an implementation. An example of this is the behavior of a transfer function if the value is set to zero.

这些标准应该是描述性的而不是指定性的。你如何实现那些函数完全取决于你 — 合约的内部实现和标准无关。标准会规定功能性需求,这规范了在特定场景下的行为,但是并未规定要如何实现。一个例子是当值被设为零时转移函数的行为。

Should you use these standards?

你应该使用这些标准吗?

Given all these standards, each developer faces a dilemma: use the existing standards or innovate beyond the restrictions they impose?

每个开发者在考虑使用这些标准的时候都面临着一个两难的境地:采用现有的标准还是摆脱标准的限制自主创新?

This dilemma is not easy to resolve. Standards necessarily restrict your ability to innovate, by creating a narrow "rut" that you have to follow. On the other hand, the basic standards have emerged from experience with hundreds of applications and often fit well with 99% of the use-cases.

这个问题并不能轻易地解决。标准会把你固定在一个很窄的『车道』上来限制你创新的空间。但是从另一个角度来看,这些总结于数百个应用的基本标准通常适用于99%的用例。 (topsun: 这些总结于数百个应用的基本标准通常适用于99%的用例。 -→ 这些总结自数百个应用的基本标准通常适用于99%的用例。)

As part of this consideration is an even bigger issue: the value of interoperability and broad adoption. If you choose to use an existing standard, you gain the value of all the systems designed to work with that standard. If you choose to depart from the standard, you have to consider the cost of building all of the support infrastructure on your own, or persuading others to support your implementation as a new standard. The tendency to forge your own path and ignore existing standards is known as "Not Invented Here" and is antithetical to the open source culture. On the other hand, progress and innovation depends on departing from tradition sometimes. It’s a tricky choice, so consider it carefully!

另外一个更大的考虑是:兼容性和广泛的使用率。如果你使用一个现有的标准,你就能够利用到所有遵循这个标准的系统。如果你不选择使用任何标准,你必须要考虑到自己开发所有基础设施的成本或者是劝说他人认可你的新标准。这种忽略现有标准而自行创造新标准的倾向被称为『非我所创』,与开源文化正好相反。在另一方面,进步和创新有时正是离经叛道的结果。所以这是个不太好抉择的问题,仔细考虑吧! (topsun:1.如果你不选择使用任何标准     -→如果你不选择使用这个标准       2.所以这是个不太好抉择的问题       -→ 所以这是个棘手的选择。 .Wikipedia "Not Invented Here" (https://en.wikipedia.org/wiki/Not_invented_here) Not invented here is a stance adopted by social, corporate, or institutional cultures that avoid using or buying already existing products, research, standards, or knowledge because of their external origins and costs, such as royalties.

维基百科 『非我所创』(https://en.wikipedia.org/wiki/Not_invented_here)

非我所创或NIH综合症(英文:Not Invented Here Syndrome),指的是社会、公司和组织中的一种文化现象,人们不愿意使用、购买或者接受某种产品、研究成果或者知识,不是出于技术或者法律等因素,而只是因为它源自其他地方。 (topsun:1.人们不愿意使用、购买或者接受某种产品、研究成果或者知识,不是出于技术或者法律等因素,而只是因为它源自其他地方。 -→ 由于来源于外面以及经费因素(比如版权)人们不愿意使用、购买既有的产品、研究成果 标准或者知识。)

Security by maturity

安全性成熟度

Beyond the choice of standard, there is the parallel choice of implementation. When you decide to use a standard, such as ERC20, you have to then decide how to implement a compatible token. There are a number of existing "reference" implementations that are broadly used in the Ethereum ecosystem. Or you could write your own from scratch. Again, this choice represents a dilemma that can have serious security implications.

除了标准的选择,实现同样也需要选择。当你决定采用某个标准时,比如说 ERC20,你必须接下来决定怎样实现一个 ERC20 兼容的代币。现在已经有一些被以太坊生态广泛使用的『参考』实现。或者你也可以从头开始自己写。这样的选择面临着能够严重影响安全性的两难境地。 (topsun:或者你也可以从头开始自己写。这样的选择面临着能够严重影响安全性的两难境地。 -→或者你也可以从头开始自己写, 但安全性有可能因此受到严重影响。)

Existing implementations are "battle tested". While it is impossible to prove that they are secure, many of them underpin millions of dollars of tokens. They have been attacked, repeatedly and vigorously. So far, no significant vulnerabilities have been discovered. Writing your own is not easy - there are many subtle ways that a contract can be compromised. It is much safer to use a well-tested broadly-used implementation. In our examples above, we used the OpenZeppelin implementation of the ERC20 standard, as this implementation is security focused from the ground up.

现有的标准是『久经沙场』的。虽然说我们不能完全证明这些标准就一定是绝对安全的,但大多数标准都支撑着价值数百万美元的代币。它们一直被不断地、猛烈地攻击着。至少到目前为止,还没有任何明显的漏洞出现。开发一个你自己的标准并不是件容易的事 — 有太多不太容易察觉的方式可以侵入你的合约。使用一个彻底测试的、广泛采用的实现是相对来说更安全的做法。在我们之前的例子里,我们使用的是 OpenZeppelin 对 ERC20 的实现,因为这个实现从头到尾都非常注重安全性。 (topsun:1.现有的标准是『久经沙场』的 -→ 现有的很多实现是『久经沙场』的 2.虽然说我们不能完全证明这些标准就一定是绝对安全的,但大多数标准都支撑着价值数百万美元的代币。 -→ 虽然说我们不能完全证明这些实现就一定是绝对安全的,但其中大多数支撑着价值数百万美元的代币。)

If you use an existing implementation you can also extend it. Again, be careful with this impulse. Complexity is the enemy of security. Every single line of code you add expands the attack surface of your contract and could represent a vulnerability lying in wait. You may not notice a problem until you put a lot of value on top of the contract and someone breaks it.

如果你使用一个现有的实现,你也可以扩展它。同样,对于这样的冲动需要保持冷静。复杂度是安全性的敌人。每一行新加入的代码都扩大了合约的被攻击面或者引入了新的等待被发现的漏洞。你可能并不能意识到某个漏洞知道你在这个合约上面放了非常多的价值,然后别人黑了你的合约。 (topsun: 你可能并不能意识到某个漏洞知道你在这个合约上面放了非常多的价值,然后别人黑了你的合约。 -→你可能并不能意识到某个漏洞直到这个合约价值高升,然后别人黑了你的合约。)

Extensions to token interface standards

对代币接口标准的扩展

The token standards discussed in this section start with a very minimal interface, with limited functionality. Many projects have created extended implementations, to support features that they need for their application. Some of these include:

�在这一章里讨论过的代币标准一开始都只有非常少的接口,能做非常少的事情。很多项目都做了自己的扩展实现来支持它们自身项目的需求。一些扩展如下: (topsun:在这一章里讨论过的代币标准一开始都只有非常少的接口,能做非常少的事情。 -→在这一章里讨论过的代币标准一开始只是一个功能有限的简易接口。)

Owner Control

Specific addresses, or set of addresses (multi-signature) are given special capabilities, such as blacklisting, whitelisting, minting, recovery etc.

拥有者控制:特定地址或者特定地址集(多重签名)被赋予特殊功能,例如黑名单、白名单、铸币、恢复等等。

Burning

A token burn is when tokens are deliberately destroyed by transfer to an unspendable address or by erasing a balance and reducing the supply.

销毁:销毁一个代币是指当代币被�故意转到某个无法使用的地址或者减少余额来达到减少供应的目的。 (topsun:或者减少余额来达到减少供应的目的 -→ 或者抹去余额来达到减少供应的目的 )

Minting

The ability to add to the total supply of tokens, at a predictable rate, or by "fiat" of the creator of the token.

铸币:以一个可预测的比率增加代币总供应量的能力,或者说代币创造者『发行』代币的能力。 (topsun:铸币:以一个可预测的比率增加代币总供应量的能力,或者说代币创造者『发行』代币的能力。 -→ 铸币:增加代币总供应量的能力,这种增长或遵循一个可预测的比率,或取决于代币创造者『发行』代币的意愿/能力。)

Crowdfunding

The ability to offer tokens for sale, for example through an auction, market sale, reverse-auction, etc.

众筹:�将代币进行售卖的能力,比如通过拍卖,市场销售,逆向拍卖等。

Caps

Pre-defined and immutable limits on the total supply, the opposite of the "minting" feature.

上限:预先设定、不可更改的对总供应量的限制,是『铸币』功能的反向功能。

Recovery "Back Doors"

Functions to recover funds, reverse transfers, or dismantle the token that can be activated by a designated address or set of addresses (multi-signature).

恢复『后门』:可以恢复资金、转账或者废除某个代币的函数,这通常可以被一个或多个指定的地址激活。 (topsun:恢复『后门』:可以恢复资金、转账或者废除某个代币的函数,这通常可以被一个或多个指定的地址激活。 -→恢复『后门』:可以恢复资金、撤销转账或者废除某个代币的函数,此类代币通常可以被一个或多个指定的地址激活。)

Whitelisting

The ability to restrict token transfers only to listed addresses. Most commonly used to offer tokens to "accredited investors" after vetting by the rules of different jurisdictions. There is usually a mechanism for updating the whitelist.

白名单:限制只能将代币转给特定列表里地址的功能。最常用在各种审查通过后提供代币给『可信任的投资人』。通常来说会有个特定机制来更新白名单。

Blacklisting

The ability to restrict token transfers by disallowing specific addresses. There is usually a function for updating the blacklist.

黑名单:限制将代币转给某些特定地址。通常来讲会有个特定机制来更新黑名单。

There are some reference implementations for many of these functions, for example in the OpenZeppelin library. Some of these are use-case specific and only implemented in a few tokens. There are, as of now, no widely accepted standards for the interfaces to these functions.

这些以上的功能很多都有参考实现,比如说 OpenZeppelin 库。但有些功能因为是场景相关的,所以只在很少的代币里实现了。现在来说这些场景相关的功能并没有一个广泛采纳的接口标准。

As previously discussed, the decision to extend a token standard with additional functionality represents a tradeoff between innovation/risk and interoperability/security.

正如之前所讨论的,是否向一个代币标准增加新功能的决策过程实际上是在权衡创新/冒险和兼容性/安全性。 (topsun:是否向一个代币标准增加新功能的决策过程实际上是在权衡创新/冒险和兼容性/安全性。 -→ 是否向一个代币标准增加新功能的决策过程实际上是在创新与冒险,兼容性与安全性之间的权衡取舍。)

Tokens and ICOs

代币和 ICO

Tokens have become an explosive development in the Ethereum ecosystem. It is likely that they will be a very important, foundational, component of all smart-contract platforms like Ethereum.

代币在以太坊生态里已经有非常爆炸性的进展。很有可能代币会成为所有像以太坊这样的智能合约平台里的一个重要的、基础的组件。

Nevertheless, the importance and future impact of these standards should not be confused with an endorsement of the current token offerings. As in any early stage technology, the first wave of products and companies will almost all fail, and some will fail spectacularly. Many of the tokens on offer in Ethereum today are barely disguised scams, pyramid schemes and money grabs.

尽管如此,�本书所认可的这些标准的重要性和对未来的影响不应该被误认为对现在这些 ICO 的背书。作为一个还处于早期的技术,第一波产品和公司基本上全部都会失败,而且有的会死得非常壮丽。很多现在在以太坊上的代币都是不加掩饰的骗局、非法传销以及抢钱工具。

The trick is to separate the long-term vision and impact of this technology, which is likely to be huge, from the short term bubble of token ICOs, which is rife with fraud. Both can be true at the same time. The token standards and platform will survive the current token mania, and then they will likely change the world.

最重要的是能区分开那些很热门的有着长期愿景和对这项技术的影响的代币,与短期流行的一些 ICO 泡沫。这两者可以同时都成立。但是代币标准和平台会最终挺过这个 ICO 狂热,并且它们很有可能改变这个世界。 (topsun: 最重要的是能区分开那些很热门的有着长期愿景和对这项技术的影响的代币,与短期流行的一些 ICO 泡沫。 -→最重要的是能区分开那些有着长期愿景和对这项技术会产生深远影响的代币,与短期流行的一些 ICO 泡沫,前者的增长潜力巨大。)