以太坊编程_II

目录

  1. 迈出第一步

  2. 与合约进行交互

  3. 现实世界中的构架和工具

2.尝试与合约进行交互

2.1以太坊智能合约介绍

阅读到这里,对以太坊已经有了基础的了解,已经与以太坊节点进行了交互,并在账户之间发送了一些交易等等。但除此之外,让以太坊如此惊艳的还有:智能合约。

正如我在简介中所说,智能合约是一个运行在以太坊虚拟机(EVM)上的程序。你可以创建智能合约来做任何你想做的事情,但是在今天,大多数智能合约都被用于像I-C-Os或者代币销售那样的众筹工具。接下来请允许我解释这些概念。

从众筹开始说起,相信这是一个你非常熟悉的概念。举办众筹活动的项目,其目的是为了开展项目而筹集资金。你可以几乎零成本的发行一种与你的项目相关的数字资产,并将其出售给任何人。这就是我们所说的初始代币发行(I-C-O)。

要想实现一个具有智能合约的I-C-O,你只需要实现使你的数字资产可交易并且有价值的逻辑。这听起来不错,这些就是以太坊代币,是以太坊生态系统中的一种数字资产。

接下来让我们试着通过一个例子来分析这些例子。

假设你的健康食品公司想要推出一种新的品牌。你决定进行一次I-C-O来筹集20,000个ETH。你用10个代币换取你收集到的每个ETH,并承诺,贡献者可以在你的商店使用这些代币购买食物。为此,你需要开发一个代币智能合约为每个贡献者存储他们相应的代币数量。

现在,假设你筹集到了这笔钱,开展了你的项目并开了你的第一家店。然后,你决定每份沙拉以1个代币的价格出售。一周以后,你的客人越来越多,但是沙拉的供应却是有限的,客人意识到这一点并开始把你的代币当作资产交易,从而提高了其市场价值。

这一过程在现实生活中实际存在,因为以太坊几乎允许任何人创建他们自己的可交易的数字资产。

2.2你的第一个智能合约

让我们看看如何建立一种基本的以太坊代币吧。我将通过这个例子介绍一些Solidity的基础知识。

请记住这个例子仅用于学习,不能使用它从事商业活动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
pragma solidity ^0.4.0;
contract MyToken {
address public creator;
uint256 public totalSupply;
mapping (address => uint256) public balances;

function MyToken() public {
creator = msg.sender;
totalSupply = 10000;
balances[creator] = totalSupply;
}

function balanceOf(address owner) public constant returns(uint256){
return balances[owner];
}

function sendTokens(address receiver, uint256 amount) public returns(bool){
address owner = msg.sender;

require(amount > 0);
require(balances[owner] >= amount);

balances[owner] -= amount;
balances[receiver] += amount;
return true;
}
}

让我们一步步来。pragma关键词显示了你使用的源码的Solidity版本。然后,用合约的名字进行合约定义初始化,在这个例子中,名字就是MyToken

接下来,你可以看到三个变量:

creator 是一个地址变量,用于存储该合约的拥有者。

TotalSupply 是一个256位的无符号整数,用于存储愿意与投资者共享的代币总数。

balance 是从地址到无符号整数的映射,其记录着每个投资者的余额。

之后,你将看到构造函数。正如你所见,这是一个与合约同名的函数,同时每当该合约的一个新的实例被部署在网络中时它将被调用一次。这就是合约的所有者被存储的地方。由于所有的函数调用都是一笔交易,因此可以通过交易的发送者即 msg.sender 获得合约的所有者信息。这个合约定义了总共10,000个代币。

下一个函数十分简单:balanceOf 用于展示参数指定的地址的余额。也许你想知道 constant 关键字是什么意思。这是因为Solidity的函数分为两种,一种是常量函数,一种是非常量函数。

非常量函数执行后状态会发生变化。另一方面,常量函数只读一次,这意味着它不执行任何状态转换,而是只读取数据。实际上,共有两种类型的常量函数:

view 声明函数承诺不修改状态(常量的别名)

pure 声明函数承诺不读取或者修改状态

sendTokens 函数允许我们在地址间交换代币。这是一个非常量函数或者说是一个交易函数,因为使用这个函数将改变余额。该函数的参数是接收者的地址以及欲转移的代币数量,函数的返回值是一个表示交易是否成功执行的布尔(Boolean)类型数据。你可以跳过第一行,它只是把函数的发送者保存在owner变量中。

接下来,你将看到两个先决条件:

1
2
3
4
...
require(amount > 0);
require(balances[owner] >= amount);
...

Require 是你可以用来检查条件或者进行验证的方法之一。它将评估一个条件并在条件不满足时恢复原状。因此,在这个例子中,要求被转移的代币 amount 大于零,同时要保证发送者有足够的余额来支付该笔代币转移。

最后,你要从 owner 的余额中减去交易转移的代币数量,并将其添加到 receiver 的余额中:

1
2
3
4
5
...
balances[owner] -= amount;
balances[receiver] += amount;
return true;
}

2.3 部署智能合约

现在让我们开始试着玩一下智能合约吧!首先需要在网络上部署智能合约。为了实现部署,需要使用名为 solc的Solidity编译器用于编译node.js。你可以通过以下指令安装它:

1
npm install -g solc

创建一个叫做 MyToken.sol 的文件并把合约代码粘贴到文件中,并在放置该文件的路径下打开一个控制终端。首先,通过运行以下指令编译文件:

1
solcjs MyToken.sol --bin

执行完该指令,编译器将创建一个 MyToken_sol_MyToken.bin 文件作为输出。你可以看到该文件只包含字节码。接着,你将需要使用solc来构建ABI(应用二进制接口),它是合约的接口或者说模板,通过它你可以获得可用的方法。这就是与Web3的联系点。你只需要运行:

1
solcjs MyToken.sol --abi

接着,你将看到一个叫做 MyTolen_sol_MyToken.abi 的新文件,其中包含的JSON内容定义了你的合约的接口。

最后,你只需要使用在后台运行的 testrpc ,通过node.js控制台部署你的合约。你完成了这些工作后,我们就开始初始化 web3 吧:

1
2
3
4
//instance web3
Web3 = require('web3')
provider = new Web3.providers.HttpProvider("http://localhost:8545")
web3 = new Web3(provider)

Web3 为你提供了解析合约ABI的可能性,并提供了一个JavaScriot API 与之交互。接着,你只需使用字节码就可以将该合约的一个新实例部署到 testrpc 上。请按照下面的命令输入:

1
2
3
4
5
6
7
8
9
10
// load files
myTokenABIFile = fs.readFileSync('MyToken_sol_MyToken.abi')
myTokenABI = JSON.parse(myTokenABIFile.toString())
myTokenBINFile = fs.readFileSync('MyToken_sol_MyToken.bin')
myTokenByteCode = myTokenBINFile.toString()
//deploy
account = web3.eth.accounts[0]
MyTokenContract = web3.eth.contract(myTokenABI)
contractData = { data: myTokenByteCode, from: account, gas: 999999 }
deployedContract = MyTokenContract.new(contractData)

最后,你可以通过调用 deployedContract.address 检查新部署的合约地址。请保存该地址,因为将需要使用这个地址与你的合约进行交互 :) 。

2.4 Web3与智能合约

让我们从搜索你的 testrpc 账户余额开始。为此,你首先需要访问已部署的合约实例:

1
2
3
4
5
6
contractAddress = deployedContract.address
instance = MyTokenContract.at(contractAddress)
web3.eth.accounts.forEach(address => {
tokens = instance.balanceOf.call(address)
console.log(address + ": " + tokens)
})

正如我们所预期的,第一个账户拥有所有的代币。太棒了!接下来,让我们将一部分代币转移到其他账户:

1
2
3
4
5
6
7
8
9
10
// send tokens
amount = 10
from = web3.eth.accounts[0]
to = web3.eth.accounts[1]
transactionHash = instance.sendTokens(to, amount, { from: from })
// checkout balances again
web3.eth.accounts.forEach(address => {
tokens = instance.balanceOf.call(address)
console.log(address + ": " + tokens)
})

你应该可以看到,现在第二个地址有了10个代币!你也可以搜索交易信息,正如你在本指南第一部分所做的那样:

1
web3.eth.getTransaction(transactionHash)

我还为这个mini DApp设计了一个简单的UI,你可以在这里看到它。你将看到一个包含了我们合约ABI的 MyToken.json 文件。我只是把solidity编译器生成的ABI的内容粘贴到这里面。你还可以看到一个与前一个应用相似的 app.js 文件,但是这个app.js文件还包含了我刚刚向你展示的用于发送代币以及展示账户代币余额详细信息的逻辑。


开发的用来测试 MyToken 转账的 DApp 的 UI

你也可以下载这个 App 并开始与它游戏。你将被要求提供你开发的合约案例的地址。

注意:为了减轻术语负担,这篇文章中描述的代币并非ERC20标准的代币。如果你不知道什么是ERC20协议代币,我们会在下一篇文章中解释它。

原文链接: https://blog.zeppelin.solutions/a-gentle-introduction-to-ethereum-programming-part-2-7bbf15e1a953

作者: Facu Spagnuolo