这篇文章将带你使用truffle套件从零开始搭建环境, 编写、部署、测试和调用智能合约。
配套代码在 https://github.com/yinhui1984/HelloTruffle
使用truffle套件
1. 安装node.js
https://nodejs.org/en/
安装完成后, 查看是否版本以确保成功
2. 安装truffle和Ganache
truffle
是一套智能合约的开发测试环境 Ganache
用于创建测试链(可以创建ETH
FILECOIN
等测试链), 用来跑自己创建的合约.
https://trufflesuite.com
使用npm安装truffle
安装完成后, 查看版本以确保成功
如果遇到permission denied: truffle
找到truffle文件
1
|
ll /usr/local/bin/truffle
|
可以看到其软连接到cli.bundled.js
1
2
|
Permissions Size User Date Modified Name
lrwxr-xr-x 48 zhouyinhui 29 Jun 14:06 /usr/local/bin/truffle -> ../lib/node_modules/truffle/build/cli.bundled.js
|
给这个js加上执行权限即可
1
|
chmod +x /usr/local/lib/node_modules/truffle/build/cli.bundled.js
|
安装Ganache
到这里直接下载即可 https://trufflesuite.com/ganache/
3. 使用truffle新建项目
新建一个目录, 比如~/Downloads/truffleTest
到这个目录下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
OSX MP16 ~/Downloads/truffleTest ❯ truffle init
Starting init...
================
> Copying project files to /Users/zhouyinhui/Downloads/truffleTest
Init successful, sweet!
Try our scaffold commands to get started:
$ truffle create contract YourContractName # scaffold a contract
$ truffle create test YourTestName # scaffold a test
http://trufflesuite.com/docs
|
其新建了这些文件
1
2
3
4
5
6
7
8
|
OSX MP16 ~/Downloads/truffleTest ❯ tree
.
├── contracts
│ └── Migrations.sol
├── migrations
│ └── 1_initial_migration.js
├── test
└── truffle-config.js
|
contracts
文件夹存放合约的地方, 在其中新建一个solidity源码文件, 比如Calculator.sol
1
|
touch ./contracts/Calculator.sol
|
在Calculator.sol
中添加如下代码来做个加减法
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Calculator {
function add(int a, int b) public pure returns (int) {
return a + b;
}
function subtract(int a, int b) public pure returns (int) {
return a - b;
}
}
|
其中的pure
表示改函数既不读也不写状态机变量 https://hashnode.com/post/pure-vs-view-in-solidity-cl04tbzlh07kaudnv1ial1gio
复制上面代码时注意 pragma solidity ^0.8.0;
这一行和 trffle-config.js
中的下面的配置相匹配
1
2
3
4
5
6
7
|
// Configure your compilers
compilers: {
solc: {
version: "0.8.15",
//....
},
}
|
pragma solidity ^0.8.0
表示编译器使用0.8
到0.9
版本之间的(不包含0.9
), 那么trffle-config.js
中配置的编译器版本要在这个范围内
4.编译
1
2
3
4
5
6
7
8
|
OSX MP16 ~/Downloads/truffleTest ❯ truffle compile
Compiling your contracts...
===========================
> Compiling ./contracts/Calculator.sol
> Artifacts written to /Users/zhouyinhui/Downloads/truffleTest/build/contracts
> Compiled successfully using:
- solc: 0.8.15+commit.e14f2714.Emscripten.clang
|
编译完成后, 会将结果放到build
目录下: 我们这里关心的是Calculator.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
OSX MP16 ~/Downloads/truffleTest ❯ tree
.
├── build
│ └── contracts
│ ├── Calculator.json
│ └── Migrations.json
├── contracts
│ ├── Calculator.sol
│ └── Migrations.sol
├── migrations
│ └── 1_initial_migration.js
├── test
└── truffle-config.js
5 directories, 6 files
|
5.部署到测试链
启动测试链
启动 ganache
App, 点击QUICKSTART
其就自动创建了ETH
测试链, 查看窗口上半部分显示的的RPC server信息, 比如 HTTP://127.0.0.1:8545
修改 truffle-config.js
中的部署配置, 使其与上面的RPC server向匹配
1
2
3
4
5
6
7
|
networks: {
//...
development: {
host: "127.0.0.1", // Localhost (default: none)
port: 8545, // Standard Ethereum port (default: none)
network_id: "*", // Any network (default: none)
},
|
Note
请保持ganache在后台运行, 后面的代码都需要访问测试链
添加部署代码
1
|
touch migrations/2_deploy_contracts.js
|
在2_deploy_contracts.js
中加入如下代码
1
2
3
4
5
|
var Calculator = artifacts.require("./Calculator.sol");
module.exports = function(deployer) {
deployer.deploy(Calculator);
}
|
部署合约
运行 truffle migrate
命令进行部署
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
OSX MP16 ~/Downloads/truffleTest ❯ truffle migrate
Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.
Starting migrations...
======================
> Network name: 'development'
> Network id: 5777
> Block gas limit: 6721975 (0x6691b7)
1_initial_migration.js
======================
Deploying 'Migrations'
----------------------
> transaction hash: 0xc4b0bf65cbac8d1cf68b72fc6408bd443c18e6bf3e44c5985dede50b191a80d1
> Blocks: 0 Seconds: 0
> contract address: 0xe2fd5fA1303B9791417EF637AABd50aE9DB9Af44
> block number: 1
> block timestamp: 1656490514
> account: 0xe149d5f732685669C9E494B233fDB4312d19b5cF
> balance: 99.99502292
> gas used: 248854 (0x3cc16)
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00497708 ETH
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00497708 ETH
2_deploy_contracts.js
=====================
Deploying 'Calculator'
----------------------
> transaction hash: 0xc8cce4b870d187dadf38353e6ef71aabd0751375a2f2b8c763ed4a35d8067a0c
> Blocks: 0 Seconds: 0
> contract address: 0x6B62e4E253823FBC65E0B93d63ee149350158a18
> block number: 3
> block timestamp: 1656490515
> account: 0xe149d5f732685669C9E494B233fDB4312d19b5cF
> balance: 99.98984578
> gas used: 216344 (0x34d18)
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00432688 ETH
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00432688 ETH
Summary
=======
> Total deployments: 2
> Final cost: 0.00930396 ETH
|
6. 测试合约
使用truffle console 手动调用
运行 truffle console
打开控制台
1
2
3
4
5
|
truffle(development)> let cal = await Calculator.deployed()
truffle(development)> cal.add(1,2)
BN { negative: 0, words: [ 3, <1 empty item> ], length: 1, red: null }
truffle(development)> cal.subtract(10, 2)
BN { negative: 0, words: [ 8, <1 empty item> ], length: 1, red: null }
|
在控制台中可以使用TAB按键来提示成员变量或函数
使用js编写单元测试
1
|
touch test/testCalculator.test.js
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
//testCalculator.test.js
//开发框架导入合约
const Calculator = artifacts.require("Calculator");
//接下来,我们定义用于测试的合约,然后将账户作为包含所有地址的参数传递。
contract("Calculator", accounts => {
//it包含对我们要运行的测试的简短描述,
// 它是一个包含所有测试相关脚本的异步函数
it("should add two numbers", async () => {
//cal: 定义存储已部署合约的实例。
const cal = await Calculator.deployed();
const result = await cal.add(4, 2);
assert.equal(result.toNumber(), 6);
}).timeout(10000);
it("should subtract two numbers", async () => {
const cal = await Calculator.deployed();
const result = await cal.subtract(3, 2);
assert.equal(result.toNumber(), 1);
}).timeout(10000);
});
|
运行 truffle test
跑测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
OSX MP16 ~/Downloads/truffleTest ❯ truffle test
Using network 'development'.
Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.
Contract: Calculator
✔ should add two numbers
✔ should subtract two numbers (48ms)
2 passing (126ms)
|
7. demoApp调用合约
使用web3.js
我们使用web3.js
创建一个app来实际使用我们的合约
初始化一个node.js app
1
2
3
|
mkdir -p ./demo/jsApp
cd ./demo/jsApp/
npm init
|
然后一路默认回车
安装web3.js
在index.js
中加入如下代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
const Web3 = require('web3');
//注意,这里有个坑, 有时候http://localhost:8545 用localhost可以,有时候连接不上
//最好用127.0.0.1
const web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:8545"));
//web3.eth.getAccounts().then(console.log)
//modify the following line to your own contract address
let contractAddress = "0x6B62e4E253823FBC65E0B93d63ee149350158a18";
//copy abi from ./build/contracts/Calculator.json
let abi = [
{
"inputs": [
{
"internalType": "int256",
"name": "a",
"type": "int256"
},
{
"internalType": "int256",
"name": "b",
"type": "int256"
}
],
"name": "add",
"outputs": [
{
"internalType": "int256",
"name": "",
"type": "int256"
}
],
"stateMutability": "pure",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "int256",
"name": "a",
"type": "int256"
},
{
"internalType": "int256",
"name": "b",
"type": "int256"
}
],
"name": "subtract",
"outputs": [
{
"internalType": "int256",
"name": "",
"type": "int256"
}
],
"stateMutability": "pure",
"type": "function",
"constant": true
}
]
let contract = new web3.eth.Contract(abi,contractAddress);
contract.methods.add(4,2).call().then(console.log);
contract.methods.subtract(3,2).call().then(console.log);
|
其中的 contractAddress是我们部署的Caculator合约地址
如何找到合约地址?
方式1, 在上面的truffle migrate
进行合约部署时,会打印
方式2, 在Ganache
软件界面的Transactions
中找, 貌似不方便
方式3, 在Ganache
软件界面 : Settings
-> Workspace
中点击ADD Project
添加 truffle-config.js
文件关联项目, 然后点击RESTART
重新回到主界面的CONTRACTS
就可以看到了
方式4, 运行truffle console
1
2
|
truffle(development)> Calculator.address
'0x6B62e4E253823FBC65E0B93d63ee149350158a18'
|
其中的abi
是合约对应的abi
如何找到abi?
方式1, solc --abi ./contracts/Calculator.sol
方式2, 到./build/contracts/Calculator.json
中, 找到"abi"
对应的值
不要使用truffle console中的Calculator.abi
运行app
1
2
3
|
OSX MP16 ~/Downloads/truffleTest/demo/jsApp ❯ node index.js
6
1
|
使用golang
1
2
3
4
|
mkdir -p ./demo/goApp
cd ./demo/goApp
touch main.go
go mod init goApp
|
需要使用到 "github.com/ethereum/go-ethereum/ethclient"
这个包
1
|
go get "github.com/ethereum/go-ethereum/ethclient"
|
这个包如果要调用合约, 则需要一些额外的操作
额外的操作: 利用 abigen
从solidity
源文件生成golang
代码
1
|
OSX MP16 ~/Downloads/truffleTest/demo/goApp ❯ mkdir contracts
|
1
|
abigen --pkg contracts --sol ../../contracts/Calculator.sol --out ./contracts/calculatorContract.go
|
- –pkg contracts : 指定生成的代码的 package name
- –sol ../../contracts/Calculator.sol : 指定合约代码位置
- –out ./contracts/calculatorContract.go: 指定生成的代码位置
生成完成后, goApp的目录结构如下:
1
2
3
4
5
6
7
|
tree ~/Downloads/truffleTest/demo/goApp
/Users/zhouyinhui/Downloads/truffleTest/demo/goApp
├── contracts
│ └── calculatorContract.go
├── go.mod
├── go.sum
└── main.go
|
生成后的calculatorContract.go
中import包,可能并不在go.mod
中, 所以运行一次 go mod tidy
以避免编译时找不到包
main.go
中代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
package main
import (
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"goApp/contracts"
"math/big"
)
//https://goethereumbook.org/client-setup/
func main() {
client, err := ethclient.Dial("http://localhost:8545")
if err != nil {
fmt.Println("could not connect to local node, err:", err)
return
}
contractAddress := common.HexToAddress("0x6B62e4E253823FBC65E0B93d63ee149350158a18")
calculator, err := contracts.NewCalculator(contractAddress, client)
if err != nil {
fmt.Println("could not instantiate contract, err:", err)
return
}
callOpts := bind.CallOpts{
Context: nil,
Pending: false,
}
a := big.NewInt(1)
b := big.NewInt(2)
result, err := calculator.Add(&callOpts, a, b)
if err != nil {
fmt.Println("could not call contract, err:", err)
return
}
fmt.Println("add result:", result)
result, err = calculator.Subtract(&callOpts, a, b)
if err != nil {
fmt.Println("could not call contract, err:", err)
return
}
fmt.Println("subtract result:", result)
}
|
运行
1
2
3
|
OSX MP16 ~/Downloads/truffleTest/demo/goApp ❯ go run .
add result: 3
subtract result: -1
|
手工撸
这里: https://yinhui1984.github.io/从零开始编写智能合约-手工打造/