上一章的结尾说这一次要讲编写一个智能合约部署到测试网络集群中,并进行交易,但我本身越看越以为内容挺多的.先讲下truffle的项目建立,编译和部署的问题,而后再作上面说的事情吧.
truffle是一套以太坊的开发测试框架, 使用solidity开发语言,相似于javascript.
truffle的安装在第一篇文章中已经讲过.下面直接开始进入truffle的使用.
Truffle建立项目
使用truffle能够很简单的建立项目:
mkdir truffle-project
truffle init
这个命令只会建立一个简单的项目,用于编写,编译,部署基于Solidity的只能合约,
若是须要建立基于Web应用的以太坊智能合约,则须要使用:
mkdir truffle-project
truffle init webpack
命令完成之后, 文件夹的目录结构应该是这样:
▾ 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库和智能合约不一样,为了更好的说明,咱们仍是把它们统称为"合约".
编译命令:
编译智能合约只须要运行下面的简单命令:
truffle默认只会编译最后一次编译成功以后被修改过的合约文件, 这是为了减小比必要的编译.设置"--all"选项,能够强制编译全部文件.
编译结果(ARTIFACTS)
编译输出的位置为相对于项目目录的"./build/contracts"目录.若是目录不存在,将会自动被建立.这些编译后的文件彻底组成了truffle的内部工做.它们为成功部署你的应用扮演了至关重要的角色.你不该该手动修改这些文件,由于即便修改了, 随着合约的编译和部署,又会被覆盖掉.
依赖项
使用Solidity的"import"命令来申明合约的依赖项. truffle将会自动按照正确的顺序来编译,并保证全部的依赖项都会发送给编译器,依赖能够经过两种方法来指定.
经过依赖项的文件名:
为了从一个独立的文件中导入合约,只须要编写下面的命令, 其中"AnotherContact.sol"文件的路径是相对于当前正在编写的合约的.这将使得"AnotherContact.sol"文件中的全部合约(类)对于当前的源代码均可用.
import "./AnotherContact.sol";
经过导出的包引入合约(类)
truffle支持经过NPM和EthPM安装的依赖库. 要从依赖库中导入合约, 使用下面的语法, "sompackage"表示经过NPM或者EthPM安装的包, "/SomeContract.sol"表示包中提供的solidity源文件的路径(包含文件名).
import "somepackage/SomeContract.sol"
注意:truffle将会先搜索EthPM,而后搜索NPM,因此在低几率状况下,出现了名字的冲突,使用的是EthPM中的库.
部署/迁移
这条命令将会执行在项目migrations目录中的全部"migrations".最简单的状况是全部的"migrations"只是一个被管理的部署脚本集合.若是执行过"migrations", "truffle migrate"只会启动那些在先前没有执行过的,新建立的"migrations".加入没有新的"migrations"存在,"truffle migrate"不会执行任何任务.可使用"--reset"选项强制全部"migrations"从新执行.当用于本地测试时,请确保TestRPC已经安装并在执行"migrate"以前运行起来.
迁移文件
一个简单的migration文件看起来是这样:
var MyContract = artifacts.require("MyContract");
module.exports = function(deployer) {
// deployment steps
deployer.deploy(MyContract);
};
请注意文件名有一个数字前缀和一个用于描述的后缀. 编码的前缀是必须的,它须要用来记录"migration"是否执行成功.后缀纯粹是为了便于阅读和理解.
ARTIFACTS.REQUIRE()
在migration文件的开始, 咱们经过"artifacts.require()"方法告诉truffle咱们将要与那个合约交互.这个方法相似于Node中的"require",但在咱们这里,它特殊的返回一个合约抽象,咱们能够在后面的部署脚本中使用它这个合约. 这个指定的变量名不是必须与合约源文件的名字相同,但它应该与在源代码中定义的合约类名称相同.考虑下面的示例, 在同一个源文件中定义了两个合约类.
文件名: ./contracts/Contracts.sol
contract ContractOne {
// ...
}
contract ContractTwo {
// ...
}
若是须要使用"ContractTwo", "artifacts.require()"语句应该是这样:
var ContractTwo = artifacts.require("ContractTwo");
MODULE.EXPORTS(模块导出)
全部"migrations"必须经过"module.exports"语法导出一个函数.每一个被"migration"导出的函数都必须有一个"deployer"对象做为函数的第一个参数.这个对象辅助部署时, 为部署智能合约提供了简洁的语法,并担当了一些普通的部署职责,如:保存部署的生成文件为以后的须要使用."deployer"对象是部署阶段的主要接口.其API在本文后面有讲解.
"migration"函数还能接受其它的参数.请参考以后的示例.
INITIAL MIGRATION(migration初始化)
truffle为了使用迁移的特性, 须要一个迁移合约.这个合约必须实现指定的接口, 但你能够根据意愿自由的编辑此合约.对于大多数项目来讲,这个合约会在第一次迁移的时候就初始部署,而且以后不会再更新.当在使用"truffle init"建立新项目的时候,你默认会获得这个合约.
文件名:contracts/Migrations.sol
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
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"来进行部署阶段的任务.因从,你能够按同步顺序编写部署代码,它们会被按照正确的顺序执行:
// Stage deploying A before B
deployer.deploy(A);
deployer.deploy(B);
或者, deployer上的每一个函数能够被用做受权许可, 以按照队列方式进行部署任务, 每一个任务都依赖前一个任务的执行结果:
// 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".例如:
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"函数.
示例:
// 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会忽略这个合约.
例如:
// 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:
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);
});
到这里咱们就应该本身可以编写合约的部署代码了.下一次讲什么,等我捋一捋....有点乱.