一、前言
Truffle 3.0 版本引入了大量的新特性,这些特性为我们带来了大量的重要革新性变化,让 network 的管理更简单,新的抽象的合约层,允许你从第三方引入各种依赖文件。 伴随以太坊的开发工具逐步成熟,这样的革新非常有价值。这个升级同样适用于从 beta 3.0.0-9 升级到 3.0.1 的用户。
二、配置(Configuration)
Truffle 2.0 中使用了一个不舒服的配置方式,不仅可以使用一个 default,匿名的网络设置(通过 rpc 配置项);同时也可以使用命名的网络设置,比如 ropsten 或者 live。由此带来的一个后果是,可能会无意中覆盖了网络配置,或者部署到错误的网络。在 Truffle 3.0 中,解决了这个问题,代价是对配置方式的调整。 在 Truffle 2.0 版本中,一个命名网络的声明方式示例如下:
module . exports = {
rpc: {
host: "localhost" ,
port: 8545
} ,
networks: {
staging: {
host: "localhost" ,
port: 8546 ,
network_id: 1337
} ,
ropsten: {
host: "158.253.8.12" ,
port: 8545 ,
network_id: 3
}
}
} ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
在 Truffle 3.0 中,修改为下述的方式:
module . exports = {
networks: {
development: {
host: "localhost" ,
port: 8545 ,
network_id: "*"
} ,
staging: {
host: "localhost" ,
port: 8546 ,
network_id: 1337
} ,
ropsten: {
host: "158.253.8.12" ,
port: 8545 ,
network_id: 3
}
}
} ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
变化点如下:
默认网络配置项 rpc 被移除,取而代之的是在 networks 配置项下的一个专门的名为 development 的网络配置项,development 中会指定的 ip 和 port,和配置的网络类型 1,默认是任意 *;
如果没有指定网络,在执行 migrate 等命令时默认使用名为 development 的网络;
为了避免出现部署到错误网络的情况,也可以完全移除配置项 development。如果已经移除 development 的网络配置,但在 truffle migrate 等命令时不指定网络会有下述错误发生:
$ truffle migrate
Compiling . / contracts/ ConvertLib. sol. . .
Compiling . / contracts/ MetaCoin. sol. . .
Compiling . / contracts/ Migrations. sol. . .
Writing artifacts to . / build/ contracts
Error: No network specified. Cannot determine current network.
at Object. detect ( / usr/ local/ lib/ node_modules/ truffle/ lib/ environment. js: 27 : 23 )
at / usr/ local/ lib/ node_modules/ truffle/ lib/ commands/ migrate. js: 33 : 19
at / usr/ local/ lib/ node_modules/ truffle/ lib/ contracts. js: 51 : 11
at / usr/ local/ lib/ node_modules/ truffle/ lib/ contracts. js: 83 : 9
truffle migrate -- network ropsten
另外每个网络配置项,都需要指定一个网络 ID[networkID],这可以算是一种安全机制来保证部署的安全性,来保证一定是部署到你想要部署的网络,development 这样的网络环境,可以使用*来表示任意,以方便调测。
三、移植和测试依赖(Migrations and Test Dependencies)
在没有引入包管理前,Truffle 可以假设所有智能合约都是需要移植和测试的。在有了包管理之后,由于依赖可以来自多个不同的地方,不需要再进行默认关联,如果需要关联,必须明确指定,以减少自动关联带来的潜在问题。 来看一个migration(移植)的手动引入关联的例子:
在 2.0 版本中的 ./migrations/2_deploy_contracts.js:
module . exports = function ( deployer) {
deployer. deploy ( ConvertLib) ;
deployer. autolink ( ) ;
deployer. deploy ( MetaCoin) ;
} ;
3.0 版本中的 ./migrations/2_deploy_contracts.js:
var ConvertLib = artifacts. require ( "ConvertLib.sol" ) ;
var MetaCoin = artifacts. require ( "MetaCoin.sol" ) ;
module . exports = function ( deployer) {
deployer. deploy ( ConvertLib) ;
deployer. link ( ConvertLib, MetaCoin) ;
deployer. deploy ( MetaCoin) ;
} ;
改进之处如下:
提供 artifacts.require() 方法,类似 Node.js 的 require() 语句,用于声明依赖;
可以通过 artifacts.require() 来引入一个本地的 Solidity 文件,或者是包中依赖路径,这个方法将负责引入对应的文件;
Javascript 中的测试用例也需要通过 artifacts.require() 语句来引入依赖。 自动关联为依赖问题的解决带来了一点点方便,但随着包管理的引入,不再可能自动判断在移植中所真正需要的所有的依赖,提供的替代方案是,通过明确指定的方式来关联库。
四、合约
① 合约抽象:JSON 格式(不再使用 .sol.js!)
Truffle 之前将合约定义为 Javascript 的格式,以 .sol.js 为后缀的文件存储。存为这样的格式的考虑,主要是可以在任何地方容易的使用。但最终发现,我们考虑的并不完善,不仅 Javascript 中存在一些情况中难以直接使用,更在非 Javascript 环境完全不可用。为解决这样的局限性,Truffle 3.0 将所有合约编译后的结果存为 JSON 格式,以能随时随地的,跨环境使用。 如果已经使用 Truffle 2.0 生成了许多的文件,我们提供了一个工具来进行转换。如果要升级 Truffle2.0 生成的 .sol.js,需要先安装 truffle-soljs-updater:
$ npm install - g truffle- soljs- updater
安装好后,可以通过 sjsu 命令来使用这个工具,使用方法如下:
$ cd . / build/ contracts
$ sjsu
Converting ConvertLib. sol. js. . .
Converting MetaCoin. sol. js. . .
Converting Migrations. sol. js. . .
Files converted successfully.
具体使用方法是,进入到 .sol.js 文件所在目录,这里是 ./build/contracts,然后运行 sjsu 即可。默认情况下 sjsu 命令只会创建新格式的 .json 后缀结尾的文件,并不会删除原始文件,需要手动删除旧的 .sol.js 来保证 Truffle 3.0 能正常工作。需要注意的是,建议删除前将旧的 .sol.js 的文件备份到其它地方,最终确认生成的 .json 文件正确的情况下再删除。 另外一种选择是使用强制模式,sjsu -f 通过加 -f 参数,这样 sjsu 会删除旧的 .sol.js 文件,并生成生的 .json 文件,但需要保证旧的文件已提前进行了妥善的保存,以避免造成不必要的损失。
$ cd . / build/ contracts
$ sjsu - f
Converting ConvertLib. sol. js. . .
Converting MetaCoin. sol. js. . .
Converting Migrations. sol. js. . .
Files converted successfully.
Successfully deleted old . sol. js files.
② 合约交互抽象层:.deployed() 现在提供 then()
这个改变,会影响 Migrations(移植)、测试、应用代码。在 Truffle2.0 中,合约抽象层使用原生的方式来管理网络。上文中的 default 的方式,引发潜在的网络不正确的可能,导致可能的部署到错误的网络的问题。 原抽象层提供了一个精心考虑,方便使用的语法,MyContract.deployed().myFunction(…),但将错误直接暴露给了开发者。在 Truffle 3.0 中,改变了这个语法,为 .deployed() 提供了类似 promise 的语法。同时,当前的合约抽象层可以与以太坊的包管理标准,EIP190eip190 进行无缝集成,但这意味着必须改变原有代码的语法。 v2.0 版本的语法:
MyContract. setProvider ( someWeb3Provider) ;
MyContract. deployed ( ) . someFunction ( ) . then ( function ( tx) {
} ) ;
MyContract. setProvider ( someWeb3Provider) ;
MyContract. deployed ( ) . then ( function ( instance) {
return instance. someFunction ( ) ;
} ) . then ( function ( result) {
} ) ;
上述的语法上可额能有些啰嗦,但目的是保证合约抽象层连接到了正确的网络。再来看看新语法的例子:
MyContract. setProvider ( someWeb3Provider) ;
var deployed;
MyContract. deployed ( ) . then ( function ( instance) {
deployed = instance;
return deployed. someFunction ( ) ;
} ) . then ( function ( result) {
return deployed. anotherFunction ( ) ;
} ) . then ( function ( result) {
} ) ;
需要注意的是:
如果合约方法 f() 不会改变区块链上的数据的,那么调用时要使用 instance.f.call();
如果合约方法 f() 会改变区块链上的数据的,则需使用 instance.f() 来进行调用;
如果想在一个 promise 链中调用多个方法,需要注意示例中的 instance 的作用域,可以在外部定义一个对象,查看 var deployed; 的定义。
③ 合约抽象层:交易结果对象
一直以来有个麻烦的事,关于在 Web3 中监听事件,在大多数情况下,事件一般不是通过主动跟踪事件,而是我们进行了对应的操作,从而引发对应的事件产生。尽管主动监听事件的方式仍然支持,但为了让后一种情况实现起来更加简单,调整了 transaction 的返回结果。 在 Truffle2.0 版本中,transaction 简单的返回了一个交易的哈希串,而在 Truffle3.0 中,将返回一个包含交易详情的结果对象。 v2.0:
MyContract. deployed ( ) . someFunction ( ) . then ( function ( tx) {
} ) ;
MyContract. deployed ( ) . then ( function ( instance) {
deployed = instance;
return deployed. someFunction ( ) ;
} ) . then ( function ( result) {
} ) ;
在 Truffle 3.0 中通过返回 transaction 的详情信息,可以更加方便的决定是否要触发某些相应的行为。如下所示,是一个假想的关注合约发布事件:
var assert = require ( "assert" ) ;
var PackageIndex = artifacts. require ( "PackageIndex.sol" ) ;
contract ( "PackageIndex" , function ( accounts) {
it ( "publishes a release correctly" , function ( ) {
return PackageIndex. deployed ( ) . then ( function ( deployed) {
return deployed. publish ( "v2.0.0" ) ;
} ) . then ( function ( result) {
var found_published_event = false ;
for ( var i = 0 ; i < result. logs. length; i++ ) {
var log = result. logs[ i] ;
if ( log. event == "ReleasePublished" ) {
found_published_event = true ;
break ;
}
}
assert ( found_published_event, "Uh oh! We didn't find the published event!" )
} ) ;
} ) ;
} ) ;
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
虽然这样,需要在所有返回结果中,去找我们感兴趣的事件,但也许是比 PackageIndex.ReleasePublished.watch(…) 更好的一种方式,因为可以在当前的代码中管理事件逻辑,而不是分散在代码各处。
五、构建流:默认不再需要构建器
Truffle1.0 和 Truffle2.0 中,将 Web 应用与整个框架紧紧的捆绑在一起,所以 Truffle 打包提供了一个默认的构建流,以便可以快速的建立 dapp,并运行起来。虽然在有些情况下,这带来了极大的便利,但在其它的场景下却显得极为鸡肋。 基于以太坊的应用持续的在增加,从开始的仅仅支持 Web 的 dapp 应用,发展为可以支持原生语言,在手机或电脑上独立运行。支持各种各样的用户场景,一直是 Truffle 的初衷,所以 Truffle3.0 决定移除默认的构建流。如果你想使用 Truffle 来集成你的应用,仍然可以编写自定义的构建流,因为 Truffle 打算专注做智能合约相关的最好用工具。因此关于构建,通过集成更好工具,如 webpack,browserify,Grunt,Metalsmith 来实现。 尽管构建流默认被移除,但并不意味着没得选择,在 Truffle 中非常注重开发者的使用体验,也永远不会对大家弃之不顾,下面提供两种可选方式,后一种选择会深度绑定到最终选择的构建工具上,如 webpack。
① 在 Truffle3.0 中使用旧的构建器
如果在 Truffle2.0 中使用了默认的构建器,而又想升级到 Truffle3.0,我们升级了默认构建器4,所以它完全可以与 Truffle3.0 兼容。但这将是最后一次升级默认的构建器,因为要使之兼容所有的场景,将是一件工程非常复杂的事,所以推荐最终选用后面提到的其它构建系统。 默认构建器,并未集成在 Truffle3.0 中,要使用它,需要安装 truffle-default-builder,并做为一个依赖进行引入,在工程目录运行下面的命令:
$ npm install truffle- default - builder -- save
一旦安装,可以在 truffle.js 配置文件中,使用默认构建器,配置文件的前后变化:
module . exports = {
build: {
"index.html" : "index.html" ,
"app.js" : [
"javascripts/app.js"
] ,
"app.css" : [
"stylesheets/app.css"
] ,
"images/" : "images/"
} ,
rpc: {
host: "localhost" ,
port: 8545
}
} ;
var DefaultBuilder = require ( "truffle-default-builder" ) ;
module . exports = {
build: new DefaultBuilder ( {
"index.html" : "index.html" ,
"app.js" : [
"javascripts/app.js"
] ,
"app.css" : [
"stylesheets/app.css"
] ,
"images/" : "images/"
} ) ,
networks: {
development: {
host: "localhost" ,
port: 8545 ,
network_id: "*"
}
}
} ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
可以发现,除了需要引入作为依赖的默认构建器包,放入构建器需要的配置文件以外,其它是完全一致的。由于默认构建器已经使用了最新的合约抽象层 5,因此上面的所有版本升级需要进行的调整,都适用使用默认构建器。
② 使用自定义构建流程/构建工具
自定义构建流程并没有想像中难以使用,真正的难点在于写一个构建流程能兼容各种各样的构建需求,这里推荐去看看适合自己项目的构建工具,比如之前提及的 webpack,browserify,Grunt,Metalsmith,最终哪个的特性能满足我们自己的需要,需要视项目及构建需求来定。无论想构建一个浏览器应用,命令行工具,JS 库,还是原生的手机应用,合约的初始化,部署合约的使用均遵循通用的流程。 当配置自定义工具或应用时,应遵循下述的流程:
编译合约文件,将生成的 .json 结果放到 ./build/contracts 目录下;
通过 truffle-contract[truffle-contract],将编译的合约结果,转为合约抽象层,来方便使用;
为合约抽象层设置 web3 provider,需要注意的是在 Metamask 和 Mist 中,环境内会自动提供,但在其它情况下,需要手动通过配置完成指定。 如下所示,集成 NodeJS:
var json = require ( "./build/contracts/MyContract.json" ) ;
var contract = require ( "truffle-contract" ) ;
var MyContract = contract ( json) ;
MyContract. setProvider ( new Web3. providers. HttpProvider ( "http://localhost:8545" ) ) ;
MyContract. deployed ( ) . then ( function ( deployed) {
return deployed. someFunction ( ) ;
} ) ;
所有的构建流程,均遵循上述的流程,核心关注点是保证自定义流程要加载所有的合约资源,并正确的设置合约抽象层。