使用 openzeppelin 開發第一個可升級智慧合約-基於Openzeppelin/cli(已經停止維護推薦用truffle)
技術標籤:區塊鏈
什麼是 OpenZeppelin
OpenZeppelin 是一套命令列工具,可在以太坊以及所有其他由 EVM 和 eWASM 支援的區塊鏈上開發,部署和運營智慧合約專案。包含一些已經寫好的經過安全驗證的智慧合約, 以及提供了編寫可升級智慧合約的方案。
簡單點就是類似腳手架。
npm install --global @openzeppelin/cli
官方網站:https://openzeppelin.com/
官方 Github:https://github.com/OpenZeppelin/openzeppelin-sdk
官方文件:https://docs.openzeppelin.com/
初始化
新建一個空目錄
mkdir start
cd start
初始化
# 下面這一步自己去一個包名, 其他預設直接回車
npm init
oz init
返回:
? Welcome to the OpenZeppelin SDK! Choose a name for your project first-smart-contract ? Initial project version 1.0.0 Project initialized. Write a new contract in the contracts folder and run 'openzeppelin deploy' to deploy it.
openzeppelin 命令等同於 oz
openzeppelin init
# 等同於
oz init
# 如果提示找不到命令使用
npx oz init
start 目錄下出現下列檔案
contracts 目錄是存放合約的資料夾, 此時還沒有檔案
編寫第一個合約
contracts 目錄下建立檔案:Box.sol
檔案內容如下
// contracts/Box.sol pragma solidity ^0.5.0; contract Box { uint256 private value; // Emitted when the stored value changes event ValueChanged(uint256 newValue); // Stores a new value in the contract function store(uint256 newValue) public { value = newValue; emit ValueChanged(newValue); } // Reads the last stored value function retrieve() public view returns (uint256) { return value; } }
編譯
oz compile
如果不報錯會返回:
oz compile
✓ Compiled contracts with solc 0.5.17 (commit.d19bba13)
start 目錄下會多出一個 build 目錄, 是編譯好的檔案, 其中Box.json
就是合約編譯後的資訊, 包含原始碼, 原始碼資訊,ABI, 位元組碼 bytecode, 以及編譯器使用的版本
│ networks.js
│ package.json
│
├─.openzeppelin
│ project.json
│
├─build
│ └─contracts
│ Box.json
│
└─contracts
.gitkeep
Box.sol
您還可以通過將引數傳遞給compile
命令來配置編譯,包括選擇編譯器版本和啟用優化:
$ npx oz compile --solc-version=0.5.12 --optimizer on
有關這些選項的詳細資訊,請參閱《使用 CLI 編譯》。
完善
# 安裝幾個 npm 包
# 包含 openzeppelin 提前寫好的合同
npm install --save-dev @openzeppelin/contracts
# Ganache 能快速執行一個本地測試區塊鏈
npm install --save-dev ganache-cli
啟動 Ganache,Ganache 將建立隨機的一組解鎖帳戶,並將其分配給以太幣。為了獲得與本指南中將使用的地址相同的地址,可以在確定性模式下啟動 Ganache:
# --deterministic 引數是使用當前目錄下的 networks.js 為配置啟動本地測試區塊鏈
$ npx ganache-cli --deterministic
Ganache 將打印出可用帳戶及其私鑰的列表,以及一些區塊鏈配置值。最重要的是,它將顯示其地址,我們將使用它來連線到它。預設情況下,它將為127.0.0.1:8545
。
請記住,每次執行 Ganache 時,它將建立一個全新的本地區塊鏈 -不會保留先前執行的狀態。
如果要持久化資料, 可以使用--db
選項, 指定一個目錄儲存區塊鏈生成資料
# 家目錄下的 data 儲存資料
npx ganache-cli --deterministic --db ~/data
返回(生成了 10 個地址和私鑰, 每個地址裡面有 100ETH, 以及 gas 的價格和限制,HTTP 的 RPC 埠是監聽在 8545 埠):
Ganache CLI v6.9.1 (ganache-core: 2.10.2)
Available Accounts
==================
(0) 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1 (100 ETH)
(1) 0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0 (100 ETH)
(2) 0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b (100 ETH)
(3) 0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d (100 ETH)
(4) 0xd03ea8624C8C5987235048901fB614fDcA89b117 (100 ETH)
(5) 0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC (100 ETH)
(6) 0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9 (100 ETH)
(7) 0x28a8746e75304c0780E011BEd21C72cD78cd535E (100 ETH)
(8) 0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E (100 ETH)
(9) 0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e (100 ETH)
Private Keys
==================
(0) 0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d
(1) 0x6cbed15c793ce57650b9877cf6fa156fbef513c4e6134f022a85b1ffdd59b2a1
(2) 0x6370fd033278c143179d81c5526140625662b8daa446c22ee2d73db3707e620c
(3) 0x646f1ce2fdad0e6deeeb5c7e8e5543bdde65e86029e2fd9fc169899c440a7913
(4) 0xadd53f9a7e588d003326d1cbf9e4a43c061aadd9bc938c843a79e7b4fd2ad743
(5) 0x395df67f0c2d2d9fe1ad08d1bc8b6627011959b79c53d7dd6a3536a33ab8a4fd
(6) 0xe485d098507f54e7733a205420dfddbe58db035fa577fc294ebd14db90767a52
(7) 0xa453611d9419d0e56f499079478fd72c37b251a94bfde4d19872c44cf65386e3
(8) 0x829e924fdf021ba3dbbc4225edfece9aca04b929d6e75613329ca6f1d31c0bb4
(9) 0xb0057716d5917badaf911b193b12b910811c1497b5bada8d7711f758981c3773
HD Wallet
==================
Mnemonic: myth like bonus scare over problem client lizard pioneer submit female collect
Base HD Path: m/44'/60'/0'/0/{account_index}
Gas Price
==================
20000000000
Gas Limit
==================
6721975
Call Gas Limit
==================
9007199254740991
Listening on 127.0.0.1:8545
由於本地測試鏈已經啟動, 這個不能關閉, 需要重新開一個終端命令列
使用 openzeppelin CLI 和鏈互動
要求區塊鏈是在執行彙總,openzeppelin CLI 使用 networks.js 配置和鏈互動, 實際就是使用的鏈提供的 RPC 介面
# 查詢有哪些地址
oz accounts
返回(結果和 ganache 啟動時輸出一致)
? Pick a network development
Accounts for dev-1586945927338:
Default: 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
All:
- 0: 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
- 1: 0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0
- 2: 0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b
- 3: 0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d
- 4: 0xd03ea8624C8C5987235048901fB614fDcA89b117
- 5: 0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC
- 6: 0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9
- 7: 0x28a8746e75304c0780E011BEd21C72cD78cd535E
- 8: 0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E
- 9: 0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e
查詢餘額
oz balance
返回(需要手動輸入一個地址):
? Enter an address to query its balance 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
? Pick a network development
Balance: 100 ETH
100000000000000000000
部署合約
# 建議使用 deploy
oz deploy
# create 已經廢棄, 不建議使用
oz create
使用oz create
返回(Call a function to initialize the instance after creating it 時選擇否)
The create command is deprecated. Use deploy instead.
Nothing to compile, all contracts are up to date.
? Pick a contract to instantiate Box
? Pick a network development
✓ Added contract Box
✓ Contract Box deployed
All implementations have been deployed
? Call a function to initialize the instance after creating it? No
✓ Setting everything up to create contract instances
✓ Instance created at 0xCfEB869F69431e42cdB54A4F4f105C19C080A601
To upgrade this instance run 'oz upgrade'
0xCfEB869F69431e42cdB54A4F4f105C19C080A601
使用oz deploy
返回:
這個會讓你選擇哪一種部署方式
? Choose the kind of deployment (Use arrow keys)
❯ regular standard non-upgradeable contract (常規不能升級的合約)
upgradeable upgradeable instance using a delegating proxy (EIP1967) (使用委託代理的可升級合約, 遵循 EIP1967)
minimal non-upgradeable minimal proxy instance (EIP1167) (不能升級的最小代理. 遵循 EIP1167)
我這裡選擇upgradeable
方便後面測試合約升級
Nothing to compile, all contracts are up to date.
? Choose the kind of deployment upgradeable
? Pick a network development
? Pick a contract to deploy Box
All implementations are up to date
? Call a function to initialize the instance after creating it? No
✓ Instance created at 0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B
To upgrade this instance run 'oz upgrade'
0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B
後面我們將使用這個合約地址:0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B
使用 openzeppelin CLI 和合約互動
合約中兩個函式,store
設定值,retrieve
查詢值
呼叫函式修改值
# 使用 oz send-tx 去傳送交易
oz send-tx
返回(呼叫合約中的 store 函式, 傳遞一個新的值,100 進去)
? Pick a network development
? Pick an instance Box at 0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B
? Select which function store(newValue: uint256)
? newValue: uint256: 100
✓ Transaction successful. Transaction hash: 0x11076136bab3a71551099b76626a9d561e72badba6e2b0d2f1c83d97f0c6d254
Events emitted:
- ValueChanged(100)
查詢值
retrieve函式使用view
修飾, 表示是隻讀函式, 只查詢區塊鏈不會去修改, 不消耗 gas, 此類函式使用call
方式呼叫
oz call
返回:
? Pick a network development
? Pick an instance Box at 0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B
? Select which function retrieve()
✓ Method 'retrieve()' returned: 100
100
使用程式設計的方式和合約互動
安裝 web3.js 和OpenZeppelin Contract Loader
npm install web3 @openzeppelin/contract-loader
在專案目錄新建目錄src
並建立檔案index.js
, 編寫測試程式碼
// src/index.js
const Web3 = require('web3');
const { setupLoader } = require('@openzeppelin/contract-loader');
async function main() {
// 連線 RPC 介面
const web3 = new Web3('http://localhost:8545');
// 測試是否能連通, 查詢賬戶列表
const accounts = await web3.eth.getAccounts();
console.log(accounts);
}
main();
執行node src/index.js
, 如果能打印出賬戶列表, 表示連通沒問題
[
'0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1',
'0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0',
'0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b',
'0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d',
'0xd03ea8624C8C5987235048901fB614fDcA89b117',
'0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC',
'0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9',
'0x28a8746e75304c0780E011BEd21C72cD78cd535E',
'0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E',
'0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e'
]
重新編寫程式碼:
// src/index.js
const Web3 = require('web3');
const { setupLoader } = require('@openzeppelin/contract-loader');
async function main() {
// 連線 RPC
const web3 = new Web3('http://localhost:8545');
const loader = setupLoader({ provider: web3 }).web3;
// 合約地址是之前使用 oz deploy 部署的那一個
const address = '0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B';
const box = loader.fromArtifact('Box', address);
// 呼叫 retrieve 函式, 使用 call 的方式
const value = await box.methods.retrieve().call();
console.log("Box value is", value);
}
main();
執行node src/index.js
,
$ node src/index.js
Box value is 100
傳送交易, 呼叫 store 函式, 將值設定為 20:
// src/index.js
const Web3 = require('web3');
const {setupLoader} = require('@openzeppelin/contract-loader');
async function main() {
// 連線 RPC
const web3 = new Web3('http://localhost:8545');
const loader = setupLoader({provider: web3}).web3;
// 合約地址是之前使用 oz deploy 部署的那一個
const address = '0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B';
const box = loader.fromArtifact('Box', address);
// 呼叫 retrieve 函式, 使用 call 的方式
let value = await box.methods.retrieve().call();
console.log("Box value Before is", value);
// 獲取賬戶列表
const accounts = await web3.eth.getAccounts();
// 使用第一個賬戶 accounts[0]來發送交易, 呼叫 store 函式, 將值設為 20, 指定 gas 為 50000,gasPrice 為 1e6
await box.methods.store(20)
.send({from: accounts[0], gas: 50000, gasPrice: 1e6});
// 再次呼叫 retrieve 函式, 檢視值是否變化
value = await box.methods.retrieve().call();
console.log("Box value After is", value);
}
main();
執行node src/index.js
,
$ node src/index.js
Box value Before is 100
Box value After is 20
編寫自動化測試
先忽略
使用公共測試網路測試
先忽略
升級智慧合約
此時合約0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B
中的value
值為 20
升級合約需要關注的點:
1. 與合約互動的地址要發生變化嗎? 如果變化, 那麼要通知所有呼叫該合約的人, 更新新的地址
2. 老合約的資料怎麼辦?
假設現在為合約新添加了一個新函式increment
, 呼叫一次就使value
值 +1, 得到新合約程式碼
// contracts/Box.sol
pragma solidity ^0.5.0;
// 引入 OpenZeppelin 已經寫好的許可權合約
import "@openzeppelin/contracts/ownership/Ownable.sol";
contract Box is Ownable {
uint256 private value;
event ValueChanged(uint256 newValue);
function store(uint256 newValue) public onlyOwner {
value = newValue;
emit ValueChanged(newValue);
}
function retrieve() public view returns (uint256) {return value;}
// 新增一個函式, 每次使 value 值 +1
function increment() public {
value = value + 1;
emit ValueChanged(value);
}
}
使用oz upgrade
去升級函式
oz upgrade
返回:
? Pick a network development
? Which instances would you like to upgrade? Choose by address
? Pick an instance to upgrade Box at 0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B
? Call a function on the instance after upgrading it? No
✓ Compiled contracts with solc 0.5.17 (commit.d19bba13)
- Contract Box or an ancestor has a constructor. Change it to an initializer function. See https://docs.openzeppelin.com/upgrades/2.6//writing-upgradeable#initializers.
- New variable 'address _owner' was inserted in contract Ownable in @openzeppelin/contracts/ownership/Ownable.sol:1. You should only add new variables at the end of your contract.
See https://docs.openzeppelin.com/upgrades/2.6//writing-upgradeable#modifying-your-contracts for more info.
- Contract Box imports ownership/Ownable.sol, GSN/Context.sol from @openzeppelin/contracts. Use @openzeppelin/contracts-ethereum-package instead. See https://docs.openzeppelin.com/cli/2.6/dependencies#linking-the-contracts-ethereum-package.
One or more contracts have validation errors. Please review the items listed above and fix them, or run this command again with the --force option.
報了 3 個錯:
# 1. 合約中有建構函式, 應當修改為 initializer 函式
- Contract Box or an ancestor has a constructor. Change it to an initializer function. See https://docs.openzeppelin.com/upgrades/2.6//writing-upgradeable#initializers.
# 2. 應該是附帶問題
- New variable 'address _owner' was inserted in contract Ownable in @openzeppelin/contracts/ownership/Ownable.sol:1. You should only add new variables at the end of your contract.
See https://docs.openzeppelin.com/upgrades/2.6//writing-upgradeable#modifying-your-contracts for more info.
# 3. 匯入的合約來自 @openzeppelin/contracts, 使用 @openzeppelin/contracts-ethereum-package 替換
- Contract Box imports ownership/Ownable.sol, GSN/Context.sol from @openzeppelin/contracts. Use @openzeppelin/contracts-ethereum-package instead. See https://docs.openzeppelin.com/cli/2.6/dependencies#linking-the-contracts-ethereum-package.
先解決第 3 個:
# 移除舊包, 做測試的話這個包先不要移除
npm remove @openzeppelin/contracts
# 安裝新包
# @openzeppelin/upgrades 也要裝, 不然會提示你 @openzeppelin/contracts-ethereum-package 依賴這個包
npm install --save-dev @openzeppelin/upgrades
npm install --save-dev @openzeppelin/contracts-ethereum-package
第 1 個問題:
https://docs.openzeppelin.com/upgrades/2.8/writing-upgradeable
您可以在 OpenZeppelin 升級中使用您的 Solidity 合同,無需對其進行任何修改(建構函式除外)。由於基於代理的可升級性系統的要求,因此在可升級合同中不能使用建構函式。要了解此限制的原因,請訪問代理。
這意味著,在將合同與 OpenZeppelin 升級配合使用時,您需要將其建構函式更改為常規函式(通常名為)
initialize
,在其中執行所有設定邏輯:
由於這種模式在編寫可升級合同時非常普遍,因此 OpenZeppelin 升級提供了一個
Initializable
基本合同,該合同具有一個initializer
修飾符來處理此問題:
使用initializer
去替代建構函式, 並引入 OpenZeppelin 提供的 Initializable 來限制它只能執行一次(像建構函式一樣工作)
重新編寫程式碼:
// contracts/Box.sol
pragma solidity ^0.5.0;
import '@openzeppelin/upgrades/contracts/Initializable.sol';
// 引入合約擁有者許可權
import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol";
contract Box is Initializable, Ownable {
uint256 private value;
event ValueChanged(uint256 newValue);
// 使用初始化函式代替建構函式
function initialize() public initializer {
// 要初始化 Ownable 合約
Ownable.initialize(msg.sender);
}
function store(uint256 newValue) public onlyOwner {
value = newValue;
emit ValueChanged(newValue);
}
function retrieve() public view returns (uint256) {return value;}
}
部署:
Call a function to initialize the instance after creating it? 選擇的是 Y, 呼叫 initialize 函式, 相當於執行了建構函式
$ oz deploy
✓ Compiled contracts with solc 0.5.17 (commit.d19bba13)
? Choose the kind of deployment upgradeable
? Pick a network development
? Pick a contract to deploy Box
✓ Contract Box deployed
All implementations have been deployed
? Call a function to initialize the instance after creating it? Yes
? Select which function * initialize()
✓ Setting everything up to create contract instances
✓ Instance created at 0x0290FB167208Af455bB137780163b7B7a9a10C16
To upgrade this instance run 'oz upgrade'
0x0290FB167208Af455bB137780163b7B7a9a10C16
再次使用node src/index.js
去呼叫, 不過要修改一下地址
// src/index.js
const Web3 = require('web3');
const {setupLoader} = require('@openzeppelin/contract-loader');
async function main() {
// 連線 RPC
const web3 = new Web3('http://localhost:8545');
const loader = setupLoader({provider: web3}).web3;
// 改為新的地址
const address = '0x0290FB167208Af455bB137780163b7B7a9a10C16';
const box = loader.fromArtifact('Box', address);
// 呼叫 retrieve 函式, 使用 call 的方式
let value = await box.methods.retrieve().call();
console.log("Box value Before is", value);
// 獲取賬戶列表
const accounts = await web3.eth.getAccounts();
// 使用第一個賬戶 accounts[0]來發送交易, 呼叫 store 函式, 將值設為 20, 指定 gas 為 50000,gasPrice 為 1e6
await box.methods.store(20)
.send({from: accounts[0], gas: 50000, gasPrice: 1e6});
// 再次呼叫 retrieve 函式, 檢視值是否變化
value = await box.methods.retrieve().call();
console.log("Box value After is", value);
}
main();
返回 (合約剛部署,value
沒有值, 所以等於 0, 後面等於 20):
$ node src/index.js
Box value Before is 0
Box value After is 20
升級合約
此時value
=20, 假設升級內容是增加一個函式increment
, 沒呼叫一次value
+1
得到新的合約程式碼:
// contracts/Box.sol
pragma solidity ^0.5.0;
import '@openzeppelin/upgrades/contracts/Initializable.sol';
// 引入合約擁有者許可權
import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol";
contract Box is Initializable, Ownable {
uint256 private value;
event ValueChanged(uint256 newValue);
// 使用初始化函式代替建構函式
function initialize() public initializer {
// 要初始化 Ownable 合約
Ownable.initialize(msg.sender);
}
function store(uint256 newValue) public onlyOwner {
value = newValue;
emit ValueChanged(newValue);
}
function retrieve() public view returns (uint256) {return value;}
// 新增一個函式, 每次使 value 值 +1
function increment() public onlyOwner {
value = value + 1;
emit ValueChanged(value);
}
}
使用oz upgrade
再次升級
當 Call a function to initialize the instance after creating it? 再次選擇 Y 時會報錯
$ oz upgrade
? Pick a network development
? Which instances would you like to upgrade? Choose by address
? Pick an instance to upgrade Box at 0x0290FB167208Af455bB137780163b7B7a9a10C16
? Call a function on the instance after upgrading it? Yes
? Select which function * initialize()
✓ Compiled contracts with solc 0.5.17 (commit.d19bba13)
✓ Contract Box deployed
All implementations have been deployed
✖ Upgrading instance at 0x0290FB167208Af455bB137780163b7B7a9a10C16 and calling 'initialize' with no arguments
✖ Upgrading instance at 0x0290FB167208Af455bB137780163b7B7a9a10C16
Proxy first-smart-contract/Box at 0x0290FB167208Af455bB137780163b7B7a9a10C16 failed to upgrade with error: Returned error: VM Exception while processing transaction: revert
選擇 N, 升級成功
$ oz upgrade
? Pick a network development
? Which instances would you like to upgrade? Choose by address
? Pick an instance to upgrade Box at 0x0290FB167208Af455bB137780163b7B7a9a10C16
? Call a function on the instance after upgrading it? No
Nothing to compile, all contracts are up to date.
All implementations are up to date
✓ Instance upgraded at 0x0290FB167208Af455bB137780163b7B7a9a10C16. Transaction receipt: 0x2b84fba55b975b97c511798eb772156aa14e7f5002cbc25d345b895d4d3dd159
✓ Instance at 0x0290FB167208Af455bB137780163b7B7a9a10C16 upgraded
看到地址沒有變化, 現在要驗證:
value
是否等於 20?, 如果等於表示合約狀態 (資料) 還在- 新增的
increment
是否存在?
修改一下src/index.js
// src/index.js
const Web3 = require('web3');
const {setupLoader} = require('@openzeppelin/contract-loader');
async function main() {
// 連線 RPC
const web3 = new Web3('http://localhost:8545');
const loader = setupLoader({provider: web3}).web3;
// 改為新的地址
const address = '0x0290FB167208Af455bB137780163b7B7a9a10C16';
const box = loader.fromArtifact('Box', address);
// 呼叫 retrieve 函式, 使用 call 的方式
let value = await box.methods.retrieve().call();
console.log("Box value Before is", value);
// 獲取賬戶列表
const accounts = await web3.eth.getAccounts();
// 使用第一個賬戶 accounts[0]來發送交易, 呼叫 store 函式, 將值設為 30, 指定 gas 為 50000,gasPrice 為 1e6
await box.methods.store(30)
.send({from: accounts[0], gas: 50000, gasPrice: 1e6});
// 再次呼叫 retrieve 函式, 檢視值是否變化
value = await box.methods.retrieve().call();
console.log("Box value After is", value);
// 呼叫 increment 函式
await box.methods.increment()
.send({from: accounts[0], gas: 50000, gasPrice: 1e6});
// 再次檢視 value 值, 此時 value 應該等於 31
value = await box.methods.retrieve().call();
console.log("Box value Increment is", value);
}
main();
返回(結果符合預期):
$ node src/index.js
Box value Before is 20
Box value After is 30
Box value Increment is 31