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
6var 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 | module.exports = function(deployer, network, accounts) { |
具体说明这里
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
63var 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
50const 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
21import "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 | $ truffle test |
配置文件
部署到Ganache 或其他链上时,配置配置文件1
2
3
4
5
6
7
8
9module.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
10pragma 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
8var 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);
});
};