上一章的结尾说这一次要讲编写一个智能合约部署到测试网络集群中,并进行交易,但我自己越看越觉得内容挺多的.先讲下truffle的项目创建,编译和部署的问题,然后再做上面说的事情吧. truffle是一套以太坊的开发测试框架, 使用solidity开发语言,类似于javascript. truffle的安装在第一篇文章中已经讲过.下面直接开始进入truffle的使用.
Truffle创建项目
使用truffle可以很简单的创建项目:
1
2
|
mkdir truffle-project
truffle init
|
这个命令只会创建一个简单的项目,用于编写,编译,部署基于Solidity的只能合约, 如果需要创建基于Web应用的以太坊智能合约,则需要使用:
1
2
|
mkdir truffle-project
truffle init webpack
|
命令完成以后, 文件夹的目录结构应该是这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
▾ truffle-project-web-orignal/
▾ app/
▸ javascripts/
▸ stylesheets/
index.html
▾ contracts/
ConvertLib.sol
MetaCoin.sol
Migrations.sol
▾ migrations/
1_initial_migration.js
2_deploy_contracts.js
▸ node_modules/
▾ test/
metacoin.js
TestMetacoin.sol
package
.json
README.md
truffle.js
webpack.config.js
|
目录结构
所有的智能合约都在./contracts目录中,默认情况下,目录中都有一个solidity编写的示例智能合约代码文件和一个示例solidity库文件.它们的扩展名都是".sol".虽然solidity库和智能合约不同,为了更好的说明,我们还是把它们统称为"合约".
编译命令:
编译智能合约只需要运行下面的简单命令:
1
|
truffle compile
|
truffle默认只会编译最后一次编译成功之后被修改过的合约文件, 这是为了减少比必要的编译.设置"--all"选项,可以强制编译所有文件.
编译结果(ARTIFACTS)
编译输出的位置为相对于项目目录的"./build/contracts"目录.如果目录不存在,将会自动被创建.这些编译后的文件完全组成了truffle的内部工作.它们为成功部署你的应用扮演了相当重要的角色.你不应该手动修改这些文件,因为即使修改了, 随着合约的编译和部署,又会被覆盖掉.
依赖项
使用Solidity的"import"命令来申明合约的依赖项. truffle将会自动按照正确的顺序来编译,并保证所有的依赖项都会发送给编译器,依赖可以通过两种方法来指定.
通过依赖项的文件名:
为了从一个独立的文件中导入合约,只需要编写下面的命令, 其中"AnotherContact.sol"文件的路径是相对于当前正在编写的合约的.这将使得"AnotherContact.sol"文件中的所有合约(类)对于当前的源代码都可用.
1
|
import
"./AnotherContact.sol"
;
|
通过导出的包引入合约(类)
truffle支持通过NPM和EthPM安装的依赖库. 要从依赖库中导入合约, 使用下面的语法, "sompackage"表示通过NPM或者EthPM安装的包, "/SomeContract.sol"表示包中提供的solidity源文件的路径(包含文件名).
1
|
import
"somepackage/SomeContract.sol"
|
注意:truffle将会先搜索EthPM,然后搜索NPM,所以在低概率情况下,出现了名字的冲突,使用的是EthPM中的库.
部署/迁移
1
|
truffle migrate
|
这条命令将会执行在项目migrations目录中的所有"migrations".最简单的情况是所有的"migrations"只是一个被管理的部署脚本集合.如果执行过"migrations", "truffle migrate"只会启动那些在先前没有执行过的,新创建的"migrations".加入没有新的"migrations"存在,"truffle migrate"不会执行任何任务.可以使用"--reset"选项强制所有"migrations"重新执行.当用于本地测试时,请确保TestRPC已经安装并在执行"migrate"之前运行起来.
迁移文件
一个简单的migration文件看起来是这样:
1
2
3
4
5
6
|
var
MyContract = artifacts.require(
"MyContract"
);
module.exports =
function
(deployer) {
// deployment steps
deployer.deploy(MyContract);
};
|
请注意文件名有一个数字前缀和一个用于描述的后缀. 编码的前缀是必须的,它需要用来记录"migration"是否执行成功.后缀纯粹是为了便于阅读和理解.
ARTIFACTS.REQUIRE()
在migration文件的开始, 我们通过"artifacts.require()"方法告诉truffle我们将要与那个合约交互.这个方法类似于Node中的"require",但在我们这里,它特殊的返回一个合约抽象,我们可以在后面的部署脚本中使用它这个合约. 这个指定的变量名不是必须与合约源文件的名字相同,但它应该与在源代码中定义的合约类名称相同.考虑下面的示例, 在同一个源文件中定义了两个合约类.
1
2
3
4
5
6
7
8
9
|
文件名: ./contracts/Contracts.sol
contract ContractOne {
// ...
}
contract ContractTwo {
// ...
}
|
如果需要使用"ContractTwo", "artifacts.require()"语句应该是这样:
1
|
var
ContractTwo = artifacts.require(
"ContractTwo"
);
|
MODULE.EXPORTS(模块导出)
所有"migrations"必须通过"module.exports"语法导出一个函数.每个被"migration"导出的函数都必须有一个"deployer"对象作为函数的第一个参数.这个对象辅助部署时, 为部署智能合约提供了简洁的语法,并担当了一些普通的部署职责,如:保存部署的生成文件为之后的需要使用."deployer"对象是部署阶段的主要接口.其API在本文后面有讲解. "migration"函数还能接受其它的参数.请参考之后的示例.
INITIAL MIGRATION(migration初始化)
truffle为了使用迁移的特性, 需要一个迁移合约.这个合约必须实现指定的接口, 但你可以根据意愿自由的编辑此合约.对于大多数项目来说,这个合约会在第一次迁移的时候就初始部署,并且之后不会再更新.当在使用"truffle init"创建新项目的时候,你默认会得到这个合约. 文件名:contracts/Migrations.sol
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
|
pragma solidity ^0.4.8;
contract Migrations {
address public owner;
// A function with the signature `last_completed_migration()`, returning a uint, is required.
uint public last_completed_migration;
modifier restricted() {
if
(msg.sender == owner) _;
}
function
Migrations() {
owner = msg.sender;
}
// A function with the signature `setCompleted(uint)` is required.
function
setCompleted(uint completed) restricted {
last_completed_migration = completed;
}
function
upgrade(address new_address) restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}
|
为了能够充分发挥Migrations的特性, 你必须在第一个migration中就部署这份合约.为达此目的,创建下面的"migration": 文件名:migration/1_initial_migration.js
1
2
3
4
5
6
|
var
Migrations = artifacts.require(
"Migrations"
);
module.exports =
function
(deployer) {
// Deploy the Migrations contract as our only task
deployer.deploy(Migrations);
};
|
从现在开始,你就可以使用增量的数字作为前缀来创建新的migration,用于部署其它的合约, 进行后面的部署步骤了.
DEPLOYER(部署)
"migration"文件将会使用"deployer"来进行部署阶段的任务.因此,你可以按照同步顺序编写部署任务的代码,它们会被按照编写时出现的先后顺序正确执行: "migration"文件将会使用"deployer"来进行部署阶段的任务.因从,你可以按同步顺序编写部署代码,它们会被按照正确的顺序执行:
1
2
3
|
// Stage deploying A before B
deployer.deploy(A);
deployer.deploy(B);
|
或者, deployer上的每个函数可以被用作授权许可, 以按照队列方式进行部署任务, 每个任务都依赖前一个任务的执行结果:
1
2
3
4
|
// Deploy A, then deploy B, passing in A's newly deployed address
deployer.deploy(A).then(
function
() {
return
deployer.deploy(B, A.address);
});
|
你可以把合约的部署编写成一个许可链, 如果你觉得这样的写法会更加清晰的话.
(NETWORK CONSIDERATIONS)网络方面的考虑
可以根据部署的目标网络运行部署方案. 这是一个高级的特性, 所以请先看看"NETWORK(http://truffleframework.com/docs/advanced/networks)"章节,再回来继续. 为了根据条件执行部署步骤, 要求编写的"migrations"接受第二个参数"network".例如:
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.
}
}
|
AVAILABLE ACCOUNTS(有效的账户)
在进行部署时, Migrations(迁移)会传递以太坊客户端和web3提供者 提供的账户列表给你使用. 这个列表与"web3.eth.getAccounts()"返回的账户列表完全一样.
DEPLOYER API(部署API)
为了简化"migrations", "deployer"包含许多可用的函数.
DEPLOYER.DEPLOY(CONTRACT, ARGS..., OPTIONS)
这个API,部署一个特定的合约,这个合约由contract对象指定, 具有可选构造参数. 这对单例模式的合约非常有用. 这会在部署后,设置合约的地址(i.e., Contract.address等于新部署的地址), 并会覆盖之前存储的任何地址. 为了快速进行部署多个合约,你可以传合约的数据,或者阵列数组.另外,最后一个参数是一个可选对象"overwrite", 它会观察单个键.如果"overwrite"被设置为 false, 当一个合约已经被部署的情况下, 当前合约就不会被部署. 这对合约地址由外部依赖提供, 这种特定场景是有用的. 需要注意在第一次调用"deploy"之前,你需要部署连接你即将部署的合约的所有依赖库.更多细节, 参考"link"函数. 示例:
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
|
// Deploy a single contract without constructor arguments
// 部署单个合约,不带任何构造参数
deployer.deploy(A);
// Deploy a single contract with constructor arguments
// 部署单个合约带有构造参数
deployer.deploy(A, arg1, arg2, ...);
// Deploy multiple contracts, some with arguments and some without.
// This is quicker than writing three `deployer.deploy()` statements as the deployer
// can perform the deployment as a single batched request.
// 部署多个合约,一些有参数,一些没有参数
// 这比一条"deployer.deploy()"语句部署一个合约快很多.
// 因为deployer可以把所有的合约部署都一次性打包提交.
deployer.deploy([
[A, arg1, arg2, ...],
B,
[C, arg1]
]);
// External dependency example:
//
// For this example, our dependency provides an address when we're deploying to the
// live network, but not for any other networks like testing and development.
// When we're deploying to the live network we want it to use that address, but in
// testing and development we need to deploy a version of our own. Instead of writing
// a bunch of conditionals, we can simply use the `overwrite` key.
// 在本示例中, 依赖项提供了一个部署到真实网络中的地址,但没有为相应的测试网络和开发网络提供.
// 当我们部署到真实网络中时,使用这个地址, 但在测试和开发网络的情况下,我们需要部署自己的版本.
// 我们可以用'overwrite'关键字, 代替编写一堆判断条件来执行部署.
deployer.deploy(SomeDependency, {overwrite:
false
});
|
DEPLOYER.LINK(LIBRARY, DESTINATIONS)
将一个已经部署好的库链接到一个或多个合约.'destinations'可以是单个合约或者由多个合约构成的数组.如果destinations中的任何合约并没有依赖被链接的库, deployer会忽略这个合约. 例如:
1
2
3
4
5
6
7
8
9
|
// Deploy library LibA, then link LibA to contract B, then deploy B.
// 部署LibA库, 然后将LibA链接到合约B, 然后再部署B
deployer.deploy(LibA);
deployer.link(LibA, B);
deployer.deploy(B);
// Link LibA to many contracts
// 将LibA链接到许多合约
deployer.link(LibA, [B, C, D]);
|
DEPLOYER.THEN(FUNCTION() {...})
这一段不知道怎么翻译才好, 大家看英语原版吧. 再不明白看示例. Just like a promise, run an arbitrary deployment step. Use this to call specific contract functions during your migration to add, edit and reorganize contract data. Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var
a, b;
deployer.then(
function
() {
// Create a new version of A
return
A.
new
();
}).then(
function
(instance) {
a = instance;
// Get the deployed instance of B
return
B.deployed():
}).then(
function
(instance) {
b = instance;
// Set the new instance of A's address on B via B's setA() function.
return
b.setA(a.address);
});
|
到这里我们就应该自己能够编写合约的部署代码了.下一次讲什么,等我捋一捋....有点乱.