一直觉得Ethereum
相关的开发工具挺繁杂的,网上关于怎么“编写、部署和调用智能合约”的教程也比较多,但这些教程基本上都是基于truffle
框架、geth
终端等工具进行合约的部署的调用。既然web3
只是nodejs
环境下的一个JavaScript
模块,我一直想通过最简单、纯粹的nodejs环境去直接使用web3
,这样能够对web3
模块有个比较立体的认识。于是,便有了这篇博文。
为形成一个完整的合约开发和部署流程,本文按照“编译合约”、“部署合约”和“调用合约”三个步骤来进行讲述。为使得文章讲述更清晰,我们使用一个简单的合约,内容如下所示:
pragma solidity ^0.4.19;
contract Book {
mapping(uint => string) books;
event printBookName(string bookName);
function registerBook(uint _bookId, string _bookName) public {
books[_bookId] = _bookName;
emit printBookName("Registered successfully!");
}
function getBook(uint _bookId) public view returns (string) {
return books[_bookId];
}
}
很多读者在按照网上的教程进行实验时,会出现各种各样的bug,主要是因为软件包版本不同,所以在以后的博客中,我都会列明实验的环境配置。
需要注意的是,我们需要部署一条私有链
供web3连接,可以采用上一篇博客中介绍的方法从头开始部署。这里我们采用一个更简单的方法,直接借助于ganache-cli
工具。
编译合约的目的是为了得到abi
和bin
,其中abi
是个json文件,bin
是二进制文件。
编译合约的方式有很多种,比较常见的是通过在线IDE remix
和终端工具solc
编译。
早期,solc
是被集成到web3
模块和geth
中的,但后来被移除了。所以一些旧的教程上的合约编译步骤可能会出现问题。
具体而言,
比较简单,省略。
假设我们之前的合约文件名为Book.sol
。
solc --abi --bin Book.sol
以下的操作都是在nodejs
终端下完成,所以在进行操作之前,需要安装nodejs
,并通过命令node
进入nodejs
终端中。需要注意的是,web3
模块的版本必须是0.20.x
左右的,如果是1.0.x
版本,在创建智能合约及以下步骤都会报错。安装0.20.7
版本的脚本为npm install [email protected]
。
部署合约的脚本如下所示
//使用web3模块
var Web3 = require('web3')
//创建web3实例,并连接私有链(假设私有链监听8545端口)
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
//创建智能合约,参数为solc编译后生成的abi
var bookContract = web3.eth.contract(/*abi*/)
//创建一个变量用于指代主账户,方便后续的操作
var account_0 = web3.eth.accounts[0]
//创建initializer,内同填充合约编译生成的bin,主要用于下一步的合约部署
var initializer = {from: account_0, data: '0x' + /*bin*/, gas: 300000}
//部署合约
var book = bookContract.new(initializer)
根据是否会更改链上数据,合约的调用分为以下两种:
举例来说,上述合约中的registerBook
方法会修改books
变量中的数据,其调用命令如下所示
book.registerSchoolsendTransaction(1, "Thinking in Java", {from: acount_0, gas: 300000})
函数的调用结果如下图所示:
此种方法一般对应于合约中的非pure非view
函数,需要消耗gas,无法直接得到函数的return
结果。关于如何返回非pure非view
函数的return
结果,将在第6节中进行介绍。该方法只会返回一个交易的id
。
举例来说,上述智能合约中的getBook
方法只是做查询工作,而不更改链上数据,其调用命令如下所示
book.getBook.call(1)
函数的调用结果如下图所示:
此种方法一般对应合约中的view
或者pure
函数,不消耗gas
,可以直接返回函数的return结果。
补充一点,任何不更改链上数据的调用也可以通过第一种方法(sendTransaction)来实现。但通过sendTransaction
来调用函数(即使是pure
或者view
函数),也只会返回transaction
的id
,如下图所示:
这种情况一般只能通过监视event
来实现,event
的定义和调用已经在合约中展示。以下介绍event
的监视命令:
// 定义event变量
var printBookNameEvent = book.printBookName()
// 监视event的发生
printBookNameEvent.watch(function(error, result){if(!error){process.stdout.write(result.args.bookName)}})
// 调用相应的函数即可触发该event,打印出相应的值
book.registerBook.sendTransaction(2, "Introduction to Algorithms", {from: account_0, gas: 300000})
以上三个步骤的执行结果分别如下图所示,其中第三张截图中的"Registered Successfully"即是event
返回的结果。