Truffle框架

— Truffle安装&使用 —

truffle

智能合约开发部署测试框架。
truffle 官网
翻墙食用

truffle安装

需要node.js环境使用npm安装
npm install -g truffle

Ganache测试客户端

和Truffle一起使用,需要有Ethereum客户端。
Truffle官方推出了Ganache作为测试客户端(前身testrpc)
当基于Ganache充分测试之后,通过官方客户端进行发布,比如Geth,Parity,Cpp-ethereum等。

truffle使用

指令 功能
truffle init 初始化一个新的工程,默认包含简单实例
trffle compile 编译工程,编译输出位于build/contracts
truffle migrate 运行部署脚本
truffle migrate --reset 当增加或者删除了某个合约后,可以执行命令重新部署合约。
truffle deploy 运行部署脚本
truffle build 基于配置文件,构建整个项目
truffle test 执行测试
truffle develop 启动测试链,使用Ganache当做测试链的话不需要使用
truffle console 启动truffle console,命令行
truffle create 帮助你创建新的合约、部署脚本、测试脚本
truffle networks 展示各个网络上部署的合约的地址
truffle watch 查看是否有代码文件修改,如果有的话,重新构建整个项目
truffle serve 启动一个本地服务器,展示该项目的代码目录和编译情况
truffle exec 在Truffle环境中执行JS脚本
truffle unbox 获取一个Truffle Box 项目
truffle version 显示Truffle 版本
truffle install 从 Ethereum Package Registry 上安装一个依赖包
truffle publish 向 Ethereum Package Registry 发布一个包

创建

truffle init:需要新建一个空的文件夹作为项目工作空间
成功后会生成几个子目录:

  • contracts/:开发者编写的智能合约
  • migrations/:存放部署脚本
  • test/:存放测试文件
  • truffle.js:Truffle默认配置文件

项目创建成功后,可以使用create命令来生成合约文件、测试文件和部署文件

truffle create <文件类型> <文件名称>

  • 文件类型:contract、test、migration
  • 文件名称:驼峰写法

引用官网、社区的模板和实例
truffle unbox <box名称>

编译

truffle compile:自动将contracts目录下的sol文件进行编译,便已生成的Artifacts(实际上是智能合约对应的ABI信息)会放在build/contracts文件夹下(没有会自动创建)。

  • –all:强制编译所有智能合约文件,即使没有修改
  • –network name:指定使用的网络,需要在配置文件中先声明这个网络的名称

如果合约文件没有改变,再次调用不会重新编译

编译合约之间的依赖

依赖同个文件夹下的合约文件

1
import "./AnotherContract.sol";

依赖相关包中的合约文件

1
import "somepackage/SomeContract.sol";

先从 EthPM 的包中引用 再从 NPM 的包总引用

部署

truffle migrate:部署合约。会自动检查有没有需要重新编译的智能合约文件。

  • –compile-all:不输钱强制重新编译所有智能合约
  • –network name:指定使用的网络名称。
  • –verbose-rpc:显示出Truffle和RPC客户端之间的通讯日志
  • –reset:从最开始一次执行所有migration
  • -f number:从指定的migration开始执行。number是指各个部署脚本的数字前缀

会根据/migrations/文件夹下存放的Truffle部署文件来将智能合约部署到Ethereum网络中。

truffle项目会默认包含一个叫做Migrations.sol的智能合约,这个合约可以讲用户执行步骤的历史记录下来

部署文件

部署文件是用JavaScript编写的脚本,支持智能合约之间的依赖关系。
部署文件文件名如下格式
4_example_migration.js

1
2
3
4
5
6
var MyContract = artifacts.require("MyContract");

module.exports = function(deployer) {
// deployment steps
deployer.deploy(MyContract);
};

artifacts.require

var MyContract = artifacts.require("MyContract");
artifacts.require类似于Node的require,但是是引入一个合约的抽象类
此处引入的名称 是 合约内的合约定义名称,而不是 合约文件名
因为一个合约文件中可以定义多个合约,如果有多个,就需要一个个引入

model.exports

module.exports = function(deployer) {

此处deployer一般为固定参数,还可以传入其他参数如network,accounts等

1
2
3
4
5
6
7
module.exports = function(deployer, network) {
if (network == "live") {
// Do something specific to the network named "live".
} else {
// Perform a different step otherwise.
}
}

1
2
3
module.exports = function(deployer, network, accounts) {
// Use the accounts within your migrations.
}

具体说明这里

deployer

deployer.deploy(MyContract);
官网API
按顺序部署

1
2
3
// 在B之前部署A
deployer.deploy(A);
deployer.deploy(B);

依赖部署

1
2
3
4
//部署A,然后部署B,传入一个新部署的地址
deployer.deploy(A).then(function() {
return deployer.deploy(B, A.address);
});

此处的依赖部署,需要在B合约中初始化导入参数A的地址,详情下面举例说明

测试

truffle test/truffle test ./path/test/file.js:前者全部测试,后者选取一个测试
支持两种文件测试

  • 支持JavaScript文件的测试:.js,.es,.es6,.jsx
  • 支持Solidity文件的测试:.sol
    两种测试方式各有千秋,所有测试文件都放在./test文件下

JS测试文件

Truffle测试是通过 Mocha 作为框架,Chai 作为断言。
步骤大致如下

  • 引入合约
  • 建立测试项
  • 返回通过或者错误

使用.then形式

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
var MetaCoin = artifacts.require("MetaCoin");

contract('MetaCoin', function(accounts) {
it("should put 10000 MetaCoin in the first account", function() {
return MetaCoin.deployed().then(function(instance) {
return instance.getBalance.call(accounts[0]);
}).then(function(balance) {
assert.equal(balance.valueOf(), 10000, "10000 wasn't in the first account");
});
});
it("should call a function that depends on a linked library", function() {
var meta;
var metaCoinBalance;
var metaCoinEthBalance;

return MetaCoin.deployed().then(function(instance) {
meta = instance;
return meta.getBalance.call(accounts[0]);
}).then(function(outCoinBalance) {
metaCoinBalance = outCoinBalance.toNumber();
return meta.getBalanceInEth.call(accounts[0]);
}).then(function(outCoinBalanceEth) {
metaCoinEthBalance = outCoinBalanceEth.toNumber();
}).then(function() {
assert.equal(metaCoinEthBalance, 2 * metaCoinBalance, "Library function returned unexpected function, linkage may be broken");
});
});
it("should send coin correctly", function() {
var meta;

// Get initial balances of first and second account.
var account_one = accounts[0];
var account_two = accounts[1];

var account_one_starting_balance;
var account_two_starting_balance;
var account_one_ending_balance;
var account_two_ending_balance;

var amount = 10;

return MetaCoin.deployed().then(function(instance) {
meta = instance;
return meta.getBalance.call(account_one);
}).then(function(balance) {
account_one_starting_balance = balance.toNumber();
return meta.getBalance.call(account_two);
}).then(function(balance) {
account_two_starting_balance = balance.toNumber();
return meta.sendCoin(account_two, amount, {from: account_one});
}).then(function() {
return meta.getBalance.call(account_one);
}).then(function(balance) {
account_one_ending_balance = balance.toNumber();
return meta.getBalance.call(account_two);
}).then(function(balance) {
account_two_ending_balance = balance.toNumber();

assert.equal(account_one_ending_balance, account_one_starting_balance - amount, "Amount wasn't correctly taken from the sender");
assert.equal(account_two_ending_balance, account_two_starting_balance + amount, "Amount wasn't correctly sent to the receiver");
});
});
});

使用async/await形式

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
const MetaCoin = artifacts.require("MetaCoin");

contract('2nd MetaCoin test', async (accounts) => {

it("should put 10000 MetaCoin in the first account", async () => {
let instance = await MetaCoin.deployed();
let balance = await instance.getBalance.call(accounts[0]);
assert.equal(balance.valueOf(), 10000);
})

it("should call a function that depends on a linked library", async () => {
let meta = await MetaCoin.deployed();
let outCoinBalance = await meta.getBalance.call(accounts[0]);
let metaCoinBalance = outCoinBalance.toNumber();
let outCoinBalanceEth = await meta.getBalanceInEth.call(accounts[0]);
let metaCoinEthBalance = outCoinBalanceEth.toNumber();
assert.equal(metaCoinEthBalance, 2 * metaCoinBalance);

});

it("should send coin correctly", async () => {

// Get initial balances of first and second account.
let account_one = accounts[0];
let account_two = accounts[1];

let amount = 10;


let instance = await MetaCoin.deployed();
let meta = instance;

let balance = await meta.getBalance.call(account_one);
let account_one_starting_balance = balance.toNumber();

balance = await meta.getBalance.call(account_two);
let account_two_starting_balance = balance.toNumber();
await meta.sendCoin(account_two, amount, {from: account_one});

balance = await meta.getBalance.call(account_one);
let account_one_ending_balance = balance.toNumber();

balance = await meta.getBalance.call(account_two);
let account_two_ending_balance = balance.toNumber();

assert.equal(account_one_ending_balance, account_one_starting_balance - amount, "Amount wasn't correctly taken from the sender");
assert.equal(account_two_ending_balance, account_two_starting_balance + amount, "Amount wasn't correctly sent to the receiver");
});

})

合约测试文件

分函数测试,直接看例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import "truffle/Assert.sol";//引入断言库
import "truffle/DeployedAddresses.sol";//引入获取合约地址的库
import "../contracts/MetaCoin.sol";

contract TestMetacoin {
function testInitialBalanceUsingDeployedContract() {
MetaCoin meta = MetaCoin(DeployedAddresses.MetaCoin());

uint expected = 10000;

Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
}

function testInitialBalanceWithNewMetaCoin() {
MetaCoin meta = new MetaCoin();

uint expected = 10000;

Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
}
}

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
$ truffle test
Compiling ConvertLib.sol...
Compiling MetaCoin.sol...
Compiling truffle/Assert.sol
Compiling truffle/DeployedAddresses.sol
Compiling ../test/TestMetacoin.sol...

TestMetacoin
✓ testInitialBalanceUsingDeployedContract (61ms)
✓ testInitialBalanceWithNewMetaCoin (69ms)

2 passing (3s)

配置文件

部署到Ganache 或其他链上时,配置配置文件

1
2
3
4
5
6
7
8
9
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 7545,
network_id: "*"
}
}
};

truffle 使用实例

部署合约依赖

在 Contract.sol 中使用 Coin 合约
Contract.sol

1
2
3
4
5
6
7
8
9
10
pragma solidity ^0.4.15;
import "./Coin.sol";

contract Contract {
Coin coin;
address owner;
function Contract(address _address) public {
coin = Coin(_address);
owner = msg.sender;
}

2_deploy_all.js部署文件:

1
2
3
4
5
6
7
8
var Coin = artifacts.require("./Coin.sol");
var Contract = artifacts.require("./Contract.sol");

module.exports = function(deployer) {
deployer.deploy(Coin).then(function(){
return deployer.deploy(Contract,Coin.address);
});
};