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.js1
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等
2
3
4
5
6
7
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);
  });
};