Web3部署智能合约

一、web3介绍

web3是一个专门与以太坊交互的node.js库。我们先回顾一下使用remix部署合约的步骤:

第一步:编写合约。

第二步:编译合约(之前我们设置了自动编译)。

第三步:部署合约,部署成功后返回合约地址。

第四步:调用合约。

remix底层就是使用了web3实现了编译、部署、调用合约的功能。那么web3是如何实现这些功能呢?看完这篇文章就一清二楚了!!!

二、web3的工作模式

Web3部署智能合约_第1张图片

首先,使用web3编译合约之后,会生成bytecode和abi接口。从上图可以看出,部署合约的时候需要用到这两个东西。

如果要调用合约中的方法,那么先要找到合约实例。通过abi接口和合约地址可以找到合约实例。

所以,无论部署合约还是查找合约实例都需要使用abi。

 

三、准备工作

(1)创建项目,配置nodejs环境。

参考“Goland工具开发NodeJs”的Node环境配置。

(2)安装编译器

在命令窗口中进入项目所在路径,然后执行下面命令安装solc和web3编译器。

>> npm init -y
>> npm install --save [email protected]
>> npm install --save [email protected]

(3)创建项目目录结构

创建一个contract文件夹,专门保存sol合约文件。另外再创建四个js文件。分别如下所示:

01_compile.js 编译合约

02_deploy.js 部署合约

03_instance.js 获取合约实例

04_interact.js 调用合约

创建后的目录结构如下图所示:

Web3部署智能合约_第2张图片

(4)创建合约

pragma solidity ^0.4.24;

contract SimpleStorage {
    string str;

    constructor(string _str) public {
        str = _str;
    }

    function setValue(string _str) public {
        str = _str;
    }

    function getValue() public view returns (string) {
        return str;
    }
}

为了防止编写合约时候出现的人为错误,建议先在remix上创建合约并测试通过后,再拷贝到项目的contracts目录下。

 

四、编译合约

solc官方提供了编译智能合约的示例,具体可以参考:https://www.npmjs.com/package/solc。

(1)打开01_compile.js文件,导入solc和fs

let solc = require('solc')
let fs = require('fs')

(2)读取合约代码

let contractCode = fs.readFileSync('./contracts/SimpleStorage.sol', 'utf-8')

(3)编译合约代码

let output = solc.compile(contractCode, 1)

output是一个json对象,其格式如下所示:

{ contracts:
   { ':SimpleStorage':
      { assembly: [Object],
        bytecode:
         '608060405234801561001057600080fd5b5060405161039238038061039283398101604052805101805161003a906000906020840190610041565b50506100dc565b82805460018160011
6156101000203166002900490600052602060002090601f016020900481019282601f1061008257805160ff19168380011785556100af565b828001600101855582156100af579182015b8281111561
00af578251825591602001919060010190610094565b506100bb9291506100bf565b5090565b6100d991905b808211156100bb57600081556001016100c5565b90565b6102a7806100eb6000396000f
30060806040526004361061004b5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166320965255811461005057806393a09352146100da575b6000
80fd5b34801561005c57600080fd5b50610065610135565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561009f57818101518382015260200161008
7565b50505050905090810190601f1680156100cc5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156100e657600080fd5b506040805160
206004803580820135601f81018490048402850184019095528484526101339436949293602493928401919081908401838280828437509497506101cc9650505050505050565b005b6000805460408
0516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101c15780601f1061019657610100808354040283
5291602001916101c1565b820191906000526020600020905b8154815290600101906020018083116101a457829003601f168201915b505050505090505b90565b80516101df9060009060208401906
101e3565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061022457805160ff1916838001178555610251565b828001600101
85558215610251579182015b82811115610251578251825591602001919060010190610236565b5061025d929150610261565b5090565b6101c991905b8082111561025d57600081556001016102675
600a165627a7a723058209a431495f0223bccfd15255ce1400f96f38db198124404ee19016ee697bf93d90029',
        functionHashes: [Object],
        gasEstimates: [Object],
        interface:
         '[{"constant":true,"inputs":[],"name":"getValue","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{
"constant":false,"inputs":[{"name":"_str","type":"string"}],"name":"setValue","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"
inputs":[{"name":"_str","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]',
...        
 } } }

从上面可以看到,output中包含了bytecode和interface(abi)的信息。

(4)执行导出

module.exports = output['contracts'][':SimpleStorage']
console.log('bytecode : ', output['contracts'][':SimpleStorage']['bytecode'])
console.log('abi : ', output['contracts'][':SimpleStorage']['interface'])

 

五、部署合约

(1)从“01_compile”中导入bytecode和interface

let {bytecode, interface} = require('./01_compile')

(2)创建Web3实例

// 1.引入web3
let Web3 = require('web3')
// 2.创建web3实例
let web3 = new Web3()
// 3.设置网络
web3.setProvider('http://localhost:7545')

http://localhost:7545是Ganache-cli客户端配置的地址。当部署合约后,可以在客户端上查询到具体的交易。

Web3部署智能合约_第3张图片

(3)创建Contract对象

let contract = new web3.eth.Contract(JSON.parse(interface))

因为interface是一个字符串,所以需要通过JSON.parse(interface)把它转换成JSON对象。

(4)部署合约

// 合约拥有者的帐号
const account = '0x4B249c138A04FaE2b48441cAAbde22bF32cB9613'

// 部署合约
contract.deploy({
    data : bytecode,
    arguments : ['helloworld']
}).send({
    from : account,
    gas : '3000000',
}).then(instance => {
    console.log("contract address : ", instance.options.address)
})

使用deploy方法部署合约的时候需要指定两个参数。data表示bytecode,arguments是合约构造函数的参数,它是一个数组。

部署完成后,记得调用send方法发起一个创建合约的交易。同样它也指定了两个参数。from代表创建合约的帐号,gas是油的数量。因为默认的gas比较少,为了让交易创建成功,建议设置gas的值高一点。

部署成功后,可以在Ganache-cli客户端查看到新创建的合约交易。

Web3部署智能合约_第4张图片

 

六、创建实例

(1)创建Web3实例

// 引入web3
let Web3 = require('web3')
// 创建web3实例
let web3 = new Web3()
// 设置网络
web3.setProvider('http://localhost:7545')

(2)指定abi和合约地址

// 从01_compile.js中复制过来
let abi = [{"constant":true,"inputs":[],"name":"getValue","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_str","type":"string"}],"name":"setValue","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_str","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]
// 在02_deploy.js中生成的合约地址
let address = '0xfC005a1aA55432E72A427383d3F5405A6D04287b'

abi和address可以查询之前步骤获取到。

(3)获取合约实例

let contractInstance = new web3.eth.Contract(abi, address)

(4)导出合约实例

// 导出合约实例
module.exports = contractInstance

导出合约实例是为了后面调用合约时候使用。

 

七、调用合约

(1)导入合约实例

let contractInstance = require('./03_instance')

(2)调用合约方法

Web3部署智能合约_第5张图片

调用合约需要使用到两个方法:call()和send()。它们都可以发送一个交易并调用方法,但是call不会改变合约状态,而send会改变合约状态。因此,调用getValue方法时候使用call,调用setValue时候,由于会改变合约数据,所以使用send。

contractInstance.methods.getValue().call().then(data => {
    console.log("data : ", data)

    // 测试合约的setValue方法
    contractInstance.methods.setValue("hello jj").send({
        from : from,
        value : 0,
    }).then(res => {
        console.log("res : ", res)
        contractInstance.methods.getValue().call().then(data => {
            console.log("data : ", data)
        })
    })
})

上面代码的可读性较差,如果使用async和await,代码改为:

let test = async() => {
    try {
        let v1 = await contractInstance.methods.getValue().call()
        console.log('v1 : ', v1)
        let res = contractInstance.methods.setValue("hello jj").send({
            from: from,
            value: 0,
        })
        console.log('res : ', res)
        let v2 = await contractInstance.methods.getValue().call()
        console.log('v2 : ', v2)
    } catch (e) {
        console.log(e)
    }
}

call()和send()方法都是返回一个Promise,因此不需要自己new Promise。

 

八、部署合约到测试网络

Web3部署智能合约_第6张图片

上图描述了如何通过Web3去连接以太坊的测试网络。从图上可以看出,如果要部署到测试网络,需要提供一个服务商(Provider)和账户助记词(Account Mnemonic)。

什么是Infura?

Infura是一个托管的以太坊节点集群,可以将你开发的以太坊智能合约部署到infura提供的节点上,而无需搭建自己的以太坊节点。

其实MetaMask背后就是使用了Infura作为以太坊供应商。

(1)注册供应商帐号

第一步:访问infura网站(https://infura.io),先注册一个帐号。注册完成后登录网站。

第二步:登录成功后,创建一个Project。

Web3部署智能合约_第7张图片

第三步:点击View Project按钮,选择网络为Ropsten,然后复制服务商的地址,后面需要用到。

Web3部署智能合约_第8张图片

(2)在命令窗口切换到项目路径下,然后执行下面命令安装truffle-hdwallet-provider包。

>> npm install [email protected] --save

(3)修改部署合约代码

第一步:打开02_deploy.js文件,引入infura包。

let hdWalletProvider = require('truffle-hdwallet-provider')

第二步:创建HDWalletProvider对象,传入两个参数:助记词和Provider的地址。

// 助记词,从metamask中复制过来
let mnemonic = 'letter debris ready dog window mountain front truth project bottom merit valley'
// 提供商地址,从infura.io网站复制过来
let providerUrl = 'https://ropsten.infura.io/v3/3d30b778a38b41df8f502d8b8e3ee37b'
// 创建HDWalletProvider对象
let provider = new hdWalletProvider(mnemonic, providerUrl)

第三步:设置Provider。

// web3.setProvider('http://localhost:7545')
web3.setProvider(provider)

第四步:修改帐号地址,这里改为动态获取帐号地址。

web3提供getAccounts方法获取帐号的api。由于该方法是一个异步方法,所以最好把它放入Promise中进行同步。

 

// 5.部署合约
/*contract.deploy({
    data : bytecode,
    arguments : ['helloworld'] // 传入合约构造函数参数
}).send({
    from : account,
    gas : '3000000',
}).then(instance => {
    console.log("contract address : ", instance.options.address)
})*/

let deploy = async () => {
    // 获取账户,调用该方法每次只会返回一个帐号
    let accounts = await web3.eth.getAccounts()
    // 创建合约实例
    let contractInstance = await contract.deploy({
        data : bytecode,
        arguments : ['helloworld']
    }).send({
        from : accounts[0],
        gas : '3000000',
    })
    console.log("contract instance address : ", contractInstance.options.address)
}

deploy()

(4)测试部署

首先在浏览器打开Metamask,并切换到Ropsten测试网络。

Web3部署智能合约_第9张图片

接着,找到View on Etherscan按钮,点击后在https://ropsten.etherscan.io中查看当前帐号详情。

Web3部署智能合约_第10张图片

找到最后一笔交易,点击交易ID查看交易详情。

Web3部署智能合约_第11张图片

至此部署完成。

你可能感兴趣的:(技术总结和分享)