使用Node.js部署智能合約(Smart Contract)

從智能合約原始檔、編譯、部署,一氣呵成

我想大部分的人應該都是為了寫智能合約(Smart Contract)而選擇使用Ethereum,在開發應用程式(Dapp)時,若能透過程式碼自動部署智能合約,就像truffle一樣,即可以省下可觀的測試時間。

本教學文適合有Node.js與Solidity基礎的讀者。

本文預設的系統架構如下圖,共有兩個角色,首先有作為測試使用的PoA chain,包含至少兩個Authority node node0, node1 。接著是以node.js開發的app,將會使用web3.js module (這是本篇的主角),透過JSON RPC APInode0 溝通。全部的流程只有下列三個步驟:

  1. 連結至 node0
  2. 編譯智能合約
  3. 透過 node0 部署至PoA chain
系統架構圖

1. 架設測試用的chain

可以依據自己的習慣使用testrpc, ethereum testnet或是參考下列文章建立一個測試用的PoA chain。

在後續的操作過程中,chain都要保持開啟的狀態,不再特別提醒。

2. 建立Node.js專案

$ mkdir deploy_contract
$ cd deploy_contract
$ npm init #一路enter到底

此時檔案結構如下:

deploy-contract
└── package.json

3. 連接至PoA chain

為了讓其他開發者能夠方便地與Ethereum node溝通,Ethereum定義了JSON RPC API介面,使用JSON-RPC 2.0標準。基本上這是一個無關傳輸協定(transport agnostic)的標準,只定義資料交換格式,所以不管是透過HTTP或是socket通訊,都應該要得到相同的結果。

因為我們使用node.js開發app,所以選擇使用web3.js模組,它完整實作了JSON RPC API,可以很方便地呼叫 node0 幫我們做事。

Step 1. 安裝web3.js模組

$ npm install web3 --save #在deploy_contract目錄裡安裝web3.js模組

Step 2. 新增index.js檔案,填入下列code

  • ethereumUri設定連接至 node0 的位址與port,一般來說JSON RPC的port是8545,但此範例是依照PoA chain的設定使用 http://localhost:8450
  • 設定web3 Provider連接至 node0 ,接著呼叫isConnected()確認連接是否成功
  • 最後取些基本資料,coinbase, getBalance, accounts,測試是否能夠順利溝通。輸出資料如下(若有按照PoA chain來設定輸出應該會一致,但balance可能會有些微差異)
    coinbase 就是 node0 的Authority account位址
    accounts 列表會有兩個,一個是 user account,另一個是Authority account。
$ node index.js //執行
connect至node的輸出結果

此時檔案結構如下:

deploy-contract
├── index.js
├── node_modules
└── package.json

4. 編譯智能合約sol檔案

這邊要使用solc.js模組來編譯智能合約sol檔案,最後取得部署合約需要的資訊:

  • ABI (Application Binary Interface)
  • bytecode

Step 1. 安裝solc.js

$ npm install solc --save

Step 2. 建立contracts資料夾

$ mkdir contracts
$ cd contracts

Step 3. 在contracts資料夾裡面新增一個BasicToken.sol檔案

填入下列code (此範例由openZeppelin專案修改而來)

此時檔案結構如下:

deploy-contract
├── contracts //新增
│ └── BasicToken.sol //新增
├── index.js
├── node_modules
└── package.json

Step 4. 開啟index.js,載入fs與solc模組,並加入編譯合約的程式碼

  • 透過fs.readFileSync讀取BasicToken.sol內容
  • 使用solc.compile編譯合約,得到一個JSON物件
  • 取出我們需要的bytecode與ABI
  • 最後印出ABI看看是否跟BasicToken.sol定義的function相同
$ node index.js //執行
輸出截圖,後面省略

5. 部署智能合約

部署合約就跟一般的交易一樣,需要發送一個交易至 node0 ,因此要付交易手續費(gas),所以需要一個有足夠ETH的帳戶,這邊使用 user 這個User account,但使用之前要透過web3.personal.unlockAccount解除鎖定。

Step 1. unlockAccount

開啟index.js,修改connect部分的程式碼,如下:

  • 宣告address為user account
const address = '0x004ec07d2329997267Ec62b4166639513386F32E'; // user
  • 呼叫web3.personal.unlockAccount(addr, passwd)解鎖user account
    addr填入欲解鎖的帳戶,即上面的 address 
    passwd填入帳戶的密碼,如PoA chain裡的 'user'
if (web3.personal.unlockAccount(address, 'user')) {
console.log(`${address} is unlocaked`);
}else{
console.log(`unlock failed, ${address}`);
}

執行

$ node index.js //執行
會看到unlocked資訊,後面省略

Step 2. 加入部署合約的code

開啟index.js,code加在最後面,如下:

  • 先預估會消耗的gas, web3.eth.estimateGas({data: bytecode}) 這邊只需要提供交易的data資訊即可。
但是,若只填入bytecode,執行時會發生下列Invalid params錯誤,這是因為Parity確實依照JSON RPC 2.0規範來實作,數值都是以 0x 起頭,而solc.js產生的bytecode並沒有 0x 因此會丟出錯誤,所以正確的參數為:
web3.eth.estimateGas({data: '0x' + bytecode})
若是連接到geth node而非parity node,應該就不會遇到這問題。
只填入bytecode會發生Invalid params錯誤
  • 使用abi建立一個contract instance
let MyContract = web3.eth.contract(abi);
  • 部署合約使用 web3.js封裝過的MyContract.new(),就會自動幫我們發送transaction至 node0

new()參數如下圖,總共有四個,其中 param1, param2 是optional,就我們的例子不需要填寫,只要給 transaction object與callback即可。

web3.js的contract.new() method

transaction object需要提供下列資訊:

{
from: address, // user account
data: '0x'+ bytecode, // 記得加 '0x'
gas: gasEstimate + 50000 // 多加一些gas,不然gas會不足
}
  • 為了等待callback,以取得transaction id與部署之後的contract address,再加入下列wait function
(function wait () {
setTimeout(wait, 1000);
})();

執行程式

$ node index.js 
輸出transaction id 與 contract address,前面省略
完整的index.js內容應該要長這樣:

6. 測試智能合約功能

我們有contract address與abi就可以使用Parity UI加入已經部署成功的合約,並且使用合約上面的功能。

礙於篇幅長度,這邊使用Parity UI來測試,下次再寫篇透過web3.js呼叫智能合約的介紹文。

Step 1. 到SETTINGS頁面開啟Contracts功能

開啟Contracts功能

Step 2. 設定已經部署成功的智能合約

至CONTRACTS頁面,選擇WATCH CONTRACT

選擇 WATCH CONTRACT
選擇Custom Contract

填入contract address, abi, 以及名稱

  • contract address,請填入node.js app輸出的address
0xaec4f25d8eb795b14f665ceb88b6fd9114c34bce
  • abi
[{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"type":"function"},{"inputs":[],"payable":false,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]
填入必要資訊,接著按ADD CONTRACT即可
成功匯入BasicToken合約

點進去之後就可以使用合約功能

查詢User的token數量,總共10,000

這樣就完成了,完整的程式碼都在這裡

Like what you read? Give yaohsin a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.