Truffle详解区块链
本文介绍一个简单的部署智能合约的方法:Truffle。

1、什么是Truffle ?
Truffle是针对基于以太坊的Solidity语言的一套开发框架。本身基于Javascript。Truffle为以太坊提供了开发环境、测试框架和资产管道(pipeline),旨在使以太坊开发更容易,使用Truffle你会得到:
内置智能合约编译、链接、部署和二进制字节码管理。
针对快速迭代开发的自动化合约测试。
可脚本化,可扩展的部署和迁移框架。
网络管理,用于部署到任意数量的公共和私有网络。
使用EthPM和NPM进行包安装管理。
用于直接合约通信的交互式控制台。
支持持续集成的可配置构建管道。
外部脚本运行程序可以在Truffle环境中执行脚本。
提供了合约抽象接口,可以直接通过var instance = Storage.deployed();拿到合约对象后,在Javascript中直接操作对应的合约函数。原理是使用了基于web3.js封装的Ether Pudding工具包。简化开发流程。
提供了控制台,使用框架构建后,可以直接在命令行调用输出结果,可极大方便开发调试。当开发基于Truffle的应用时,我们推荐使用 EthereumJS TestRPC。它是一个完整的运行在内存中的区块链,仅仅存在于你开发设备上。它在执行交易时是实时返回的,而不等待默认的出块时间,这样你可以快速验证你新写的代码,当出现错误时,也能即时反馈给你。它同时还是一个支持自动化测试的功能强大的客户端。Truffle充分利用它的特性,能将测试运行时间提速近90%。最好使用TestRPC客户端充分测试后,再使用这些客户端。这些是完整的客户端实现,包括挖矿,网络,区块及交易的处理,Truffle可以在不需要额外配置的情况下发布到这些客户端。
下面我们从一个简单的例子开始了解一下Truffle。
2、安装Truffle

安装完成后执行下面的命令,确保Truffle被正确的安装:

3. 创建并初始化项目

初始化完成后的目录结构如下:

contracts/ – 存放我们编写的合约。
migrations/ – 存放迁移部署脚本。
test/ – 存放合约测试脚本
truffle.js – Truffle的配置文件
truffle init会给我们创建一个名叫MetaCoin的代币应用。我们将这个默认的应用删除,我们将编写自己的合约。

4. 创建合约
接下来我们创建一个我们自己的合约,进入contracts目录,创建Storage.sol合约文件。

Storage.sol合约的内容如下:

5. 编译合约
接下来使用truffle compile命令编译刚刚完成的Storage.sol合约

从控制台的输出中,我们可以看到合约编译后的文件(artifacts)会写入./build/contracts目录中,这些合约编译后的文件对于Truffle框架能否正常工作至关重要。请不要手动修改这些文件,因为即使修改了,再次执行编译命令时又会被覆盖掉。
Truffle默认只编译自上次编译后被修改过的合约,目的是为了减少不必要的编译。如果你想编译全部合约 ,可以使用–all选项。

合约编译完成后,我们需要部署Storage.sol合约,在truffle中部署合约需要用到迁移脚本。下面我们进入migrations目录中为Storage合约创建一个迁移脚本。
6. 迁移合约
迁移脚本是由一些Javascript文件组成,用来帮助你把合约发布到以太坊网络中。之所以需要迁移脚本是因为你的部署需求会随着时间改变。随着你的项目的发展,你可以创建新的迁移脚本把这些变化的合约部署到区块链上。之前你运行的迁移历史记录,会被一个特殊的Migrations.sol合约记录在区块链上,后面将对Migrations.sol合约进行详细介绍。
移脚本的命名规则:文件名以数字开头,一个描述性的后缀结尾。数字前缀是必须的,用于记录移植是否成功。后缀仅是为了提高可读性,以方便理解。

文件:2_storage_migration.js

6.1 artifacts.require()
在迁移脚本开头,我们通过artifacts.require()方法告诉truffle我们将要与那个合约交互。这个方法类似于NodeJs中的require,但在这里,它返回的是一个合约抽象,我们可以在我们的迁移脚本的其余部分中使用这个合约抽象。artifacts.require()中使用的名字不是必须与合约源文件的文件名相同,相反,它应该与在合约源代码中定义的合约类的名称相同。
6.2 module .exports
在迁移脚本最后,我们通过module.exports到处一个函数,被迁移脚本导出的函数都应该接受一个 deployer 对象作为其第一个参数。deployer对象中的辅助函数在部署过程中提供了一种清晰的语法,用于部署智能合约,以及执行一些常见的任务,比如把发布后的对象保存下来供以后使用。这个 deployer 对象是部署任务的主接口,它的API在本文后面有讲解。
好了,我们的一切准备工作都做好了,接下来我们就可以把Storage.sol部署到我们的区块链上了,在上一章中我们把智能合约部署到geth私有链中,这次我们将把智能合约部署到Testrpc环境中。
如果你还没有安装Testrpc 那么先执行下面的安装命令:

启动testrpc

testrpc启动成功后,回到myproject项目的目录中,执行迁移命令

truffle migrate命令会执行所有的位于migrations目录内所有的迁移脚本。如果你之前已成功执行过迁移脚本,那么truffle migrate仅会执行新创建的迁移。如果没有新的迁移脚本,这个命令不会执行任何操作。可以使用选项–reset来重新执行全部迁移脚本。

6.3 初始化迁移合约
在本节开头我们提到过一个特殊的Migrations.sol合约,那么现在就来详细了解下这个特殊合约。为了使用迁移功能,Truffle要求你要有一个迁移合约。这个合约必须包含一个特定的接口,对于大多数项目来说,这个合约只会在第一次做迁移的时候被部署,以后都不会做任何的更改了。当你使用 truffle init 来创建一个项目的时候,它会默认创建这个合约。
文件名:contracts/Migration.sol

为了利用迁移的特性,你必须首先要部署Migration.sol合约。为此,创建以下迁移脚本:
文件名:migrations/1_initial_migrations.js

要部署其他合约,你可以递增数字编号前缀来创建新的迁移脚本。
6.4 部署器(deployer)
你的迁移脚本会使用这deployer对象来组织部署任务。deployer对象会同步执行部署任务,因此你可以按顺序编写部署任务。

另外,deployer上的每一个函数都会返回一个promise,通过promise可以把有执行顺序依赖关系的部署任务组成队列。

6.5 deployer API
deployer对象包含许多方法,可以用来简化你的迁移工作。
(1) deployer.deploy(CONTRACT, ARGS…, OPTIONS)
这个API是用来部署合约的,contract参数传入需要部署的合约名字,args参数传入合约的构造函数需要的参数,options是一个可选参数它的值是{overwrite: true/false}, 如果 overwrite 被设置成 false, 那么当这个合约之前已经部署过了,这个deployer就不会再部署这个合约,这在当一个合约的依赖是由一个外部合约地址提供的情况下是有用的。
为了快速进行部署多个合约,你可以向deployer.deploy(…..)函数中传入一个或多个数组。
例子:

(2) deployer.link(LIBRARY, DESTINATIONS)
把一个已部署好的库链接到一个或多个合约里. destinations 可以传入一个合约,也可以传入一组合约. 如果 destinations 中的某个合约不依赖这个库, 那deployer 的link函数就会忽略这个合约。

(3) deployer.then(function() {…})
在迁移过程中使用它调用特定合约的函数来部署新的合约,为已部署的合约做一些初始化工作等。
例子:

(4) 网络相关
在执行迁移时,迁移脚本会把truffle.js里配置的networks传递给你,你可以在module.exports导出函数中第二个参数位置接受这个值。
文件:truffle.js

例子:

(5) 可用的账户
在执行迁移时,迁移脚本会把当前以太坊客户端或web3.provider中可用的账户列表传递给你,这个列表与web3.eth.getAccounts()返回的账户列表完全一样。你可以在module.exports导出函数中第三个参数位置接受这个值。

7. 合约交互
以太坊中将向以太坊网络写入数据和从以太坊网络中读取数据这两种操作做了区分。一般来说,写数据被称为交易(transaction),而读取数据称为调用(call)。交易和调用的处理方式非常不同,并且具有以下特征。
7.1 交易(transaction)
交易会从根本上改变了网络的状态。简单的交易有:发送以太币到另一个账户。复杂的交易有:调用一个合约的函数,向网络中部署一个合约。交易的显著特征是:
交易可以写入或修改数据;
交易花费以太币运行,就是我们所说的gas;
交易需要时间处理。
当你通过交易调用合约的函数时,我们将无法立即获得智能合约的返回值,因为该交易当前只是被发送,离被打包、执行还有一段时间。通常,通过交易执行的函数将不会立刻返回值,它们将返回一个交易ID。所以总结一下,一个交易一般有如下特征:
消耗gas(以太币)
更改网络的状态
不会立即处理
不会立刻返回一个返回值(只有一个交易ID)。
7.2 调用(CALL)
另一方面,调用则完全不一样。调用可以在网络上执行代码,但不会永久更改数据。调用可以免费运行,不需要花费gas。调用的显著特征是:调用是用来读取数据。当你通过调用执行合约函数时,你将立即收到返回值。总而言之,调用是:
是免费的(不消耗gas)
不会更改网络的状态
会被立即处理
会立刻返回一个值
决定使用交易还是调用,依据很简单:要读取数据还是写入数据。
7.3 合约抽象
合约抽象是Javascript和以太坊合约交互的中间层粘合剂。简而言之,合约抽象帮我们封装好了代码,它可以让你和合约之间的交互变得简单,从而让你不必关心合约调用细节。Truffle通过truffle-contract模块来使用自己的合约抽象。合约抽象中的函数和我们合约中的函数是一样的。
为了使用合约抽象和合约交互,我们需要通过npm安装truffle-contract模块

7.4 与合约交互
7.4.1. Call 方式交互
介绍完上述概念后,现在我们可以和之前部署好的Storage.sol合约交互了,首先我们以call方式调用合约。
文件:call.js

注意:
我们必须明确地调用.call()函数,告诉Ethereum网络知道我们不会修改区块链上的数据。
当调用成功是,我们会收到一个返回值,而不是交易ID。
7.4.2 ransaction 方式交互
接下来我们以transaction方式给Storage.sol合约中storedData变量赋值为42
文件:transaction.js

上面的代码有一些需要说明的地方:
我们直接调用这合约抽象的 set 方法。 默认情况下,这个操作会向区块链网络中发送一笔交易。也可以显式调用storageInstance.set.sendTransaction(42,{from:Storage.web3.eth.accounts[0]}),表明是以transaction方式交互
当这个交易成功发出后,回调函数只有在交易被成功打包处理后才会激活,这省去了你自己写判断语句检查交易状态的麻烦。
我们传递了一个对象给set函数的第二个参数。注意:在我们的Storage.sol合约代码中set函数并没有第三个参数,这第三个参数是合约抽象API里的。在合约抽象的所有函数中,你都可以向它们传入一个对象作为最后一个参数,在这个对象中你可以写入一些有关交易细节,在这个例子中,我们在对象中写入from字段,以确保这个交易是来自 web3.eth.accounts[0]。
7.5 添加一个新合约到网络
在上面的所有例子中,我们使用的是一个已部署好的合约抽象,我们可以使用合约抽象的.new()函数来部署自己的合约。
文件:new.js

1.TMT观察网遵循行业规范,任何转载的稿件都会明确标注作者和来源;
2.TMT观察网的原创文章,请转载时务必注明文章作者和"来源:TMT观察网",不尊重原创的行为TMT观察网或将追究责任;
3.作者投稿可能会经TMT观察网编辑修改或补充。

