《第一行代码:以太坊》开始连载了

在上文中已经使用了Remix环境运行和测试了本书编写的第一个智能合约程序,不过编写和测试智能合约的测试方式很多,例如,在testrpc环境测试;在Intellij IDEA集成开发环境中用Solidity语言编写智能合约;在纯Web环境中测试智能合约;使用AJAX方式测试智能合约等。本文将详细介绍这些用于编写和测试智能合约的方法。

1.安装本地remix环境(Windows、Mac OS X和Linux)

在本节使用Remix环境运行和测试了Calc智能合约,不过使用的是在线Remix环境。由于某些原因(如没有网络,或网络速度很慢),我们希望使用本地的Remix环境运行和测试智能合约,这就要就将Remix环境安装在本地。Remix是跨平台的,所以本节介绍的安装方法同时适用于Windows、Mac OS X和Linux。

不管是在什么操作系统下安装Remix,都必须安装Node.js,读者可以到https://nodejs.org下载Node.js的最新版直接安装即可。

安装完Node.js后,需要使用git命令下载Remix的代码库(browser-solidity),命令行如下:

git clone https://github.com/ethereum/browser-solidity

在Mac OS X和Linux下,一般会集成git命令,但在Windows下,默认是没有git命令的,所以需要到下面的页面下载Windows版的git工具,下载完后直接安装即可。

https://git-scm.com/download/win

使用git命令下载完Remix的代码库后,使用cd命令进入browser-solidity目录,该目录在下载Remix代码库的过程中自动在当前目录中创建。

在browser-solidity目录中执行下面的命令安装browser-solidity。

npm install

安装browser-solidity的过程比较漫长,读者要耐心等待。下图是在Windows下安装browser-solidity环境的效果。

image.png

如果成功安装了browser-solidity,可以使用下面的命令启动Remix服务。

npm start

下图是Mac OS X下启动Remix服务后的输出信息,Windows和Linux会输出类似的信息。

image.png

Remix服务默认的端口号是8080,如果在浏览器地址栏中输入下面的Url,就可以使用本地的Remix环境编写和测试智能合约。

http://localhost:8080

2. 安装testrpc

testrpc与geth不同,geth是真正的以太坊环境,而testrpc是在本地模拟的一个以太坊环境,主要用于开发调试。当智能合约使用testrpc调试通过后,可以部署在真正的以太坊环境中。
安装testrpc仍然需要Node.js环境,所以读者应该事先安装好Node.js,然后使用下面的命令安装testrpc。
npm install -g ethereumjs-testrpc

安装好testrpc后,可以使用testrpc命令运行testrpc。下图是Mac OS X下启动testrpc服务的效果。

image.png

下图是Windows下启动testrpc服务的效果。

image.png

我们可以看到,不管是在哪一个平台上启动testrpc服务,都会自动生成10个账号(Accounts)和10个私钥(Private Keys)。这些账号和私钥都是用于测试的,而且每一个账号拥有的以太币几乎是无限大的,因此,不用担心进行某些操作后没有以太币可用。

testrpc本身是一个服务,默认的端口号是8545,这个端口号是用于像web3.js、web3.py一样的程序库连接以太坊节点的,testrpc其实也相当于一个用于测试的以太坊节点。

3.使用testrpc测试智能合约

本节会将智能合约部署到testrpc服务上,然后使用web3.js连接testrpc服务,并调用智能合约中的函数。具体的操作步骤如下:

(1)编写智能合约

启动本地的Remix环境,然后在Remix环境中输入下面的智能合约代码。

本例编写了一个名为Factorial的智能合约程序,在该智能合约中有一个factorial函数,用于计算n的阶乘。

pragma solidity ^0.4.0;
contract Factorial 
{
    /*  计算n的阶乘  */
   function factorial(uint n) returns (uint)
   {
        if (n == 0 || n == 1)
            return 1;
        else
            return n * factorial(n - 1);
    }
}

这个智能合约用于计算n的阶乘。

(2)将智能合约部署在testrpc节点上

在Remix环境的右侧进入“Run”页面,并在“Environment”列表中选择“Web3 Provider”,如下图所示。

image.png

在Web3 Provider环境下,Remix可以将智能合约直接部署到testrpc服务上。进入Web3 Provider之前,会弹出一个对话框,询问是否连接以太坊节点,单击“OK”按钮,会弹出如下图所示的对话框。在该对话框中有一个文本框,默认值是http://localhost:8545,如果要连接本地的testrpc节点或以太坊节点,直接单击“OK”按钮即可。如果testrpc节点已经启动,那么Remix本地环境会成功连接到testrpc节点上。

image.png

单击“Run”页面的“Deploy”按钮,会将Factorial智能合约部署到testrpc上。部署成功后,会在“Run”页面的下方出现“factorial”按钮,如下图所示。在按钮右侧的文本框输入要计算阶乘的n的值,然后点击该按钮即可在以太坊测试环境(testrpc)下执行factorial函数,不过在日志区域点击“Details”按钮后,并没有看到factorial函数的输出结果,这是因为factoria函数是直接在以太坊网络中运行的,所有的数据都存在于以太坊网络中,并不会直接将数据返回给以太坊客户端。

在“factorial”按钮的上方是Factorial智能合约的地址,如果在客户端要访问这个智能合约,需要使用这个地址。

image.png

(3)安装Solidity编译器

Solidity编译器是用于编译Solidity源代码文件(.sol文件)的,可以将Solidity源代码文件编译成多种目标文件。使用下面的命令行可以安装Solidity编译器。

npm install -g solc

(4)编译Solidity源代码文件

在当前目录创建一个Factorial.sol文件,然后将例3.2中的代码复制到Factorial.sol文件中。接下来会使用上一步安装的Solidity编译器对Factorial.sol文件进行编译。要注意,尽管安装的是solc,但编译器命令行工具是solcjs。这个工具可以将Solidity源代码文件编译成多种目标文件,对于本例来说,只需要abi文件即可,该文件是智能合约的接口文件。也就是说,使用Web3.js调用智能合约,需要使用abi文件才能调用智能合约中函数。

使用下面的命令可以将Factorial.sol文件编译生成abi文件。其中--abi是命令行参数,表示生成的目标文件类型是abi。

solcjs --abi Factorial.sol

执行完上面的命令后,会在当前目录生成一个Factorial_sol_Factorial.abi文件,该文件就是Factorial.sol对应的abi文件。

(5)安装Web3.js

在使用Web3.js之前必须安装Web3.js,Web3.js是Node.js的一个模块,所以需要使用下面的命令安装。

npm install web3

使用上面的命令会安装web3的最新版,如果读者使用web3最新版不太习惯,可以使用下面的命令安装指定版本。

npm install web3@0.20.6

(6)用Web3.js连接testrpc节点

现在执行node命令进入Node.js的REPL环境(命令行交互环境),然后在Node的REPL环境执行下面的命令。要注意,在执行这些命令之前,要先启动testrpc节点,并且利用Remix环境将例3.2中的智能合约部署到testrpc节点上。

> var Web3 = require("web3");
> var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); 
undefined
> var eth = web3.eth
undefined
> var abi = JSON.parse(fs.readFileSync("Factorial_sol_Factorial.abi").toString());
undefined
> var contract = eth.contract(abi);
undefined
> var instance = contract.at('0x371f45db1a077bbcbeb50d2a21bc85e4e18c1f1f')
undefined
> instance.factorial.call(3)
{ [String: '6'] s: 1, e: 0, c: [ 6 ] }
> instance.factorial(10, {from:eth.accounts[0]})
'0xbb291fec53c4c5aefc87e2d7e8475c4abd4c54d03ef06e857665a10db0c1a3ff'

上面的内容中“>”表示命令提示符,后面是输入的代码,下面是输出值,undefined是Node输出的,表示当前语句什么也没有输出(定义变量的JavaScript语句不会输出任何东西)。从这几行代码可以了解通过Web3.js连接testrpc节点的核心步骤(与连接以太坊节点的步骤相同)如下。

(1)导入web3模块,代码如下:

var Web3 = require("web3");

(2)创建Web3类的实例,并通过该类的构造方法参数指定testrpc节点的Url(IP和端口号),代码如下:

var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));

(3)读取Factorial_sol_Factorial.abi文件的内容,并将文件的内容转换为JSON对象,代码如下:

var abi = JSON.parse(fs.readFileSync("Factorial_sol_Factorial.abi").toString());

(4)使用abi创建智能合约对象,代码如下:

var contract = eth.contract(abi);

(5)将智能合约与testrpc中部署的智能合约绑定,代码如下:

var instance = contract.at('0x371f45db1a077bbcbeb50d2a21bc85e4e18c1f1f')

其中at方法的参数值就是图3-12所示的factorial方法上方的智能合约地址,也是以太坊中唯一能定位特定智能合约的标识。点击地址右侧的按钮可以将该地址复制到剪贴板上。

(6)本地调用智能合约中的factorial函数,代码如下:

instance.factorial.call(3)

本地调用智能合约,不会对以太坊网络造成任何影响。本地调用智能合约中的函数会直接输出函数的返回值,如果函数返回的是数值类型,会以BigNumber类型返回,这是一个JavaScript扩展,允许JavaScript操作任何的数值,BigNumber类型会在本书后面的章节详细讲解。

执行上面的代码,会输出如下内容,很明显,3的阶乘是6。

[String: '6'] s: 1, e: 0, c: [ 6 ] }

(7)在以太坊网络上调用智能合约,代码如下:

instance.factorial(10, {from:eth.accounts[0]})

在以太坊网络上调用智能合约的函数不会在客户端直接得到函数的返回值,而会得到一个如下的交易地址。

0xbb291fec53c4c5aefc87e2d7e8475c4abd4c54d03ef06e857665a10db0c1a3ff

因为任何在以太坊网络上进行的操作都被视作一次交易,既然有交易,就需要有交易地址,可以通过相应的API根据交易地址查询交易情况。在以太坊网络中有很多类型的地址,如矿工地址、智能合约地址、交易地址等。每一类地址都由若干位十六进制数组成,但不同类型地址的位数可能不同。

在真正的以太坊网络中,任何交易都需要矿工挖矿进行处理,同时每一笔交易会给与完成工作的矿工一定的奖励,也就是矿工的挖矿所得。不过在testrpc节点中由于是模拟以太坊网络和挖矿,所以不需要挖矿,直接会执行以太坊网络上的操作,因此,如果客户端连接的是testrpc节点,发起交易后,会立刻执行。另外,在以太坊网络上调用智能合约,需要指定是谁(一个表示用户的地址)发起的交易,因为在实际的以太坊网络中,要从这个地址扣除相应的以太币给矿工。本例使用eth.accounts[0]指定的地址。其中eth.accounts可以获取testrpc节点启动时生成的10个测试账户的地址,eth.accounts[0]就是第一个测试账户的地址。

从本节的案例来看,客户端访问以太坊网络的步骤就是连接以太坊节点和发起交易两步,当然,以太坊网络要处理交易,就需要矿工挖矿(争夺处理交易的权利,同时获得回报)了。
3.2.4 Intellij IDEA Solidity插件
不管是Remix,还是Windows记事本,或是其他的文本编辑器,都不会用于开发复杂的智能合约,一是界面并不友好,二是也没有必要的智能提示功能,而且如果智能合约的代码量很大,可能会造成Remix死掉。所以前面介绍的工具只是为了测试智能合约的,并不是用来开发实际的智能合约项目的。如果要开发大型的智能合约项目,通常会使用本地的IDE,如Intellij IDEA。这款IDE最初是为开发Java项目推出的,不过由于Intellij IDEA支持第三方插件,所以从理论上,Intellij IDEA可以支持任何的编程语言。

可能很多读者对Intellij IDEA并不熟悉,实际上,这款IDE就是大名鼎鼎的JetBrains公司推出的,如果不了解JetBrains以及它的产品,那么对Android和Google推出的Android开发工具Android Studio一定不陌生,Android Studio就是在Intellij IDEA社区版的基础上开发的。而且JetBrains公司还开发出了大名鼎鼎的Kotlin语言,现在已经成为开发Android App的官方推荐编程语言。

读者可以到下面的页面下载Intellij IDEA的免费版本(社区版)。
https://www.jetbrains.com/idea/download

Solidity语言同样提供了Intellij IDEA插件,建议使用在线安装方式。如果是Mac OSX版本的Intellij IDEA,单击左上角的IntelliJ IDEA菜单的Preferences菜单项,如下图所示。

image.png

如果是Windows版的Intellij IDEA,需要单击File菜单的Settings菜单项。单击该菜单项后,会弹出偏好(设置)窗口,如下图所示。

image.png

Preferences窗口中间的列表列出了Intellij IDEA已经安装的所有插件。单击窗口下方的Browse repositories按钮,会弹出Browse Repositories窗口,在窗口左上角的文本框中输入Solidity,会在线搜索相关的插件,如下图所示,如果找到,会在右侧显示当前选中插件的详细信息,如果没有安装该插件,会在右侧显示install按钮,单击install按钮即可安装插件。

image.png

安装完插件后,在Intellij IDEA中创建一个Java或其他工程(Solidity插件并没有提供Solidity工程),然后在工程右键菜单中单击new菜单项,会显示如下图所示的子菜单。在子菜单上会找到一个Smart contract菜单项。

image.png

单击Smart contract菜单项,会显示如下图所示的New Solidity File窗口,从Kind列表框可以选择Solidity文件类型(Smart contract或Solidity library),本例选择Smart contract。

image.png

在Name文本框中输入Solidity文件名后,单击OK按钮创建Solidity文件。然后在Intellij IDEA左侧的工程树中双击刚才创建的Solidity文件,会在右侧显示代码编辑区域,并输入如下图所示的Solidity代码。

image.png

尽管可以在Intellij IDEA中编写Solidity代码,也支持代码高亮显示和智能提示,但编译Solidity源代码文件仍然需要切换到终端,使用solcjs命令编译,很麻烦,所以在下一节会教大家如何将solcjs命令集成进Intellij IDEA,无需切换到终端就可以编译Solidity源代码文件。

5.将Solidity编译工具与Intellij IDEA集成

Intellij IDEA有一个扩展工具功能,可以将可执行程序与Intellij IDEA集成,也就是说,不用切换到终端,就可以执行这些程序。

现在打开偏好窗口(Windows中是设置窗口),在左侧区域找到Tools > External Tools节点,单击该节点后,会在右侧显示当前集成的扩展工具列表,默认是空。然后单击该区域下方“+”按钮,会弹出一个Create Tool窗口,在该窗口需要填写如下4个字段。

  • Name:solidity
  • Program:solcjs
  • Parameters:--abi --bin $FileName$ -o $OutputPath$
  • Working directory:$FileDir$

填写后的效果如下图所示,最后单击OK按钮创建扩展工具。

image.png

创建扩展工具应该了解如下几点。

  • Name只是用于显示的扩展工具名字,可以任意指定,甚至可以与已经存在的扩展工具重名。
  • Program指定的solcjs命令要在终端可以直接执行,否则会出现无法执行该命令的错误。所以在创建扩展工具之前,先要使用npm install -g solc命令安装solcjs。
  • Parameters表示solcjs的命令行参数,其中--abi表示将Solidity源代码文件编译成接口文件(.abi文件),--bin表示将Solidity源代码文件编译成二进制文件(.bin文件),用于发布智能合约。尽管这两类文件并不是在任何时候都需要,但为了省事,干脆将它们一起生成吧。
  • -o表示生成的目标文件(.abi和.bin文件)的路径。
  • $FileName$、$OutputPath$和$FileDir$都是Intellij IDEA提供的环境变量,$FileName$表示当前选择的文件名,$OutputPath$表示文件的输出目录,$FileDir$表示当前选择文件所在的目录。

如果是在Mac OS X下,$OutputPath$指向工程目录的out子目录,与工程相关的生成文件都放在这个目录中,目录结构与src目录相同。图3-20是out目录的结构,注意,读者机器上的目录结构可能有差异,但.abi和.bin文件都在out/production目录或其子目录中。

image.png

如果在Windows下,并不能执行solcjs文件,因为这个文件是在Mac OS X和Linux使用的,Windows下是solcjs.cmd,所以要将Program改成solcjs.cmd。而Windows版的Intellij IDEA并没有内置的$OutputPath$变量,所以可以将这个变量改成其他的值,如$FileDir$,这样以来,就会在.sol文件同一个目录生成.abi和.bin文件。所以Windows版的Intellij IDEA需要按下面的内容设置扩展工具。

  • Name:solidity
  • Program:solcjs.cmd
  • Parameters:--abi --bin $FileName$ -o $FileDir$
  • Working directory:$FileDir$

按前面的方式设置完扩展工具后,选中一个.sol文件(假设文件名是MyCalc.sol,里面的智能合约名是Calc),在Intellij IDEA的Tools > External Tools 菜单中出现了一个solidity菜单项,如图3-21所示,单击该菜单项,就会调用solcjs编译MyCalc.sol文件,并在相应的目录生成MyCalc_sol_Calc.abi和MyCalc_sol_Calc.bin文件。

image.png

其实在工程的右键菜单中也可以找到External Tools > solidity菜单项,如下图所示,单击该菜单项,效果是一样的。

image.png