全面了解 Truffle Migrations(迁移)的使用
通过本文可以学习到:
Migrations.sol
有啥作用?Migrations(迁移)是开发人员使数据及其支持结构的一套自动化部署的方法, 它们对于管理新软件版本的部署非常有用,因此并非仅限于区块链开发。
Truffle migrations 可以让我们把合约推到以太坊链上(不管是本地网络、测试网络还是主网),以及与其他合约链接或使用初始化合约数据。
migrations(迁移)真正最赞的地方是对区块链上合同地址的管理。 Truffle 几乎完全抽象化这项乏味的工作。
确保安装好了 Truffle Framework 和 Ganache CLI.
新建(或选择)一个项目目录,然后运行 truffle init
,会有类似下面的输出:
Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!
Commands:
Compile: truffle compile
Migrate: truffle migrate
Test contracts: truffle test
init 命令在当前目录中初始化了一个 Truffle 项目。 目录看起来像这样:
.
├── contracts
│ └── Migrations.sol
├── migrations
│ └── 1_initial_migration.js
├── test
├── truffle-config.js
└── truffle.js
在 contracts
目录下, 创建一个合约文件: Storage.sol
代码如下:
pragma solidity > 0.4.21;
contract Storage {
mapping (string => string) private _store;
function addData(string key, string value) public {
require(bytes(_store[key]).length == 0);
_store[key] = value;
}
function removeData(string key) public returns (string) {
require(bytes(_store[key]).length != 0);
string prev = _store[key];
delete _store[key];
return prev;
}
function changeData(string key, string newValue) public {
require(bytes(_store[key]).length != 0);
_store[key] = newValue;
}
}
你可以能已经看到了在运行 truffle init
时生成的Migrations.sol
和 1_initial_migration.js
。
初始迁移合约一般不需要修改,他们是跟踪部署在区块链上的地址。当然也可以按照自己的需要修改 Migrations.sol
合约文件,进行一些高级的迁移管理,但需要保留truffle init
命令创建的接口。
1_initial_migration.js
迁移文件,仅仅是说明如何把 Migrations.sol
合约部署到对应的链上。
1_initial_migration.js
迁移文件名,前面的序号,代表着truffle migrate 运行迁移文件的顺序,1
表示第一个运行的迁移文件(从 1 开始)。 我们可以创建其他的迁移文件: 2_mycontract_migration.js
,在每个合约部署完成,Truffle 会把迁移序号保存到 Migrations 合约的 last_completed_migration
假设 migrations
就这两个迁移文件,truffle migrate
运行时实际会发生 4 笔交易:
1_initial_migration.js
进行部署2_mycontract_migration.js
进行部署last_completed_migration
表示的是最后部署的迁移,之后再加入其它的迁移文件:3_yourcontract.js
时, 运行truffle migrate
时Truffle 会首先读取 last_completed_migration
状态变量,参看之前部署到了哪些,在部署比 last_completed_migration
序号大的(所有)迁移文件,这样就可以保证不会重复部署。
注意,如果修改一个已有的合约,需要重新部署的话,直接运行 truffle migrate
是不会自动部署的,需要新加(或修改)一个更高序号的迁移文件,再运行 truffle migrate
。
truffle migrate
可以接一个-f 序号
来强制重从一个需要开始执行迁移(此时会忽略 last_completed_migration
的值)。 例如: truffle migrate -f 2
会从第 2 个迁移文件开始部署。
为了将智能合约部署到以太坊区块链,首先需要编写迁移文件,在migrations
目录下,创建一个迁移文件 2_deploy_contracts.js
, 现在项目的结构如下:
.
├── contracts
│ ├── Migrations.sol
│ └── Storage.sol
├── migrations
│ ├── 1_initial_migration.js
│ └── 2_deploy_contracts.js
├── test
├── truffle-config.js
└── truffle.js
使用migrations 部署合约,需要读取合约的artifacts(构建)。构建文件会描述合同部署的网络及地址以及合约所包含的函数等。
而 artifacts(构建) 的数据从哪来呢?
在项目目录下,运行 truffle compile
, 如果没有错误的话,会有下面的输出:
Compiling ./contracts/Migrations.sol...
Compiling ./contracts/Storage.sol...
Writing artifacts to ./build/contracts
或许在不同的编译器版本下,可以会有一些警告,只要没有错误就 OK 。
再次查看项目的目录结构,会多出一个build 目录,如下:
.
├── build
│ └── contracts
│ ├── Migrations.json
│ └── Storage.json
├── contracts
│ ├── Migrations.sol
│ └── Storage.sol
├── migrations
│ ├── 1_initial_migration.js
│ └── 2_deploy_contracts.js
├── test
├── truffle-config.js
└── truffle.js
build
目录包含两个文件:Migrations.json
和 Storage.json
他们对应着 contracts
目录下的两个合约文件。
*.json
文件描述了他们对应的合约,包含:
这个文件使 Truffle 能够创建一个JavaScript 包装器(即 truffle-contract)来与智能合约通信。例如,当在JavaScript代码中调用contract.address
时,Truffle 框架从 *.json
文件读取地址,并让我们方便的在不同的合约版本和网络之间进行转换。
有了这些知识,让我们写下我们的第一个迁移文件 2_deploy_contracts.js
, 代码如下:
// 获取对应的合约文件
var Storage = artifacts.require("./Storage.sol");
// JavaScript export
module.exports = function(deployer) {
// deployer 是用来部署
// 部署
deployer.deploy(Storage);
}
编写迁移就这么简单。 为了运行迁移脚本,请在终端中运行以下命令:
truffle migrate
不过这时候,会得到一个错误:
Error: No network specified. Cannot determine current network.
意思是 Truffle 找不到要部署到的网络,这时可以在命令行终端打开一个新的 tab 运行 ganache-cli
来启动一个模拟的区块链,启动后,有会类似输出:
Ganache CLI v6.9.0 (ganache-core: 2.10.0)
Available Accounts
==================
(0) 0x828da2e7b47f9480838f2077d470d39906ad1d8e
(1) 0xa4928865329324560185f1c93b5ebafd7ae6c9e8
(2) 0x957b8b855bed52e11b2d7e9b3e6427771f299f3f
(3) 0xf4b6bcabedaf1ccb3d0c89197c4b961460f1f63d
...
Private Keys
==================
(0) 8729d0f1d876d692f2f454f564042bd11c1e6d0c9b1808954f171f6f7b926fd6
(1) 452dfeee16e5a0e34fa5348f0ef11f39a8b4635e5f454f77fc228ca9598f6997
(2) 9196ad9fd6234f09ee13726cb889dcbc438c15f98e8ff1feb36a93758fa6d10a
(3) fa47edd832e896314544b98d7e297ac2ce2097b49f8a9d7e7ae0e38154db8760
....
HD Wallet
==================
Mnemonic: void august badge future common warfxlb ...
Base HD Path: m/44'/60'/0'/0/{account_index}
Listening on localhost:8545
现在已经建立了一个私有区块链,它运行在localhost:8545
上。现在让我们配置 Truffle 以便部署合约到该网络。
打开 truffle-config.js
文件,加入以下内容:
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 8545,
network_id: "*"
}
}
};
上面配置的含义是把编译的合约部署到 localhost:8545
所在的网络上。
现在运行 truffle migrate
, 得到以下输出:
Using network 'development'.
Running migration: 1_initial_migration.js
Deploying Migrations...
... 0x06595c0eccde8cb0cf642df07beefea11e3e96bfb470e8tinyxiong6567cecc37aed8
Migrations: 0x6008e9a2c213d51093d0f18536d1aa3b00a7e058
Saving successful migration to network...
... 0x392fb34c755970d1044dc83c56df6e51d5c4d4011319f659026ba27884126d7b
Saving artifacts...
Running migration: 2_deploy_contracts.js
Deploying Storage...
... 0xb8ec575a9f3eca4a11a3f61170231a1816f7c68940d8487e56567adcf5c0a21e
Storage: 0xd8e2af5be9af2a45fc3ee7cdcb68d9bcc37a3c81
Saving successful migration to network...
... 0x15498a1f9d2ce0f867b64cdf4b22ddff56f76xlbcd3d3a92b03b7aa4d881bac
Saving artifacts...
Truffle 将合约迁移到网络并保存了构件(artifacts)。 在构建目录的 Storage.json
文件中,通过检查 networks
对象来检查它是否正确。 您应该看到类似以下内容:
"networks": {
"1525343635906": {
"events": {},
"links": {},
"address": "0xd8e2af5be9af2a45fc3ee7cdcb68d9bcc37a3c81",
"transactionHash": "0xb8ec575a9f3eca4a11a3f61170231a1816f7c68940d8487e56567adcf5c0a21e"
}
}
1525343635906
是网络的 ID,以太坊网络主网和测试网都有固定的 ID(主网是 1)。
address
是部署合约的地址。
transactionHash
是部署合约的交易hash。
在后面会看到如何使用。
Truffle 迁移的真正亮点是能进行多个合约的编译、部署和跟踪(几乎所有区块链项目都是这样)。
迁移文件不仅允许我们使用单个命令部署多个合约,还允许我们调用合约的函数,如获取这些函数的返回值并将其传递给后续合约。
我们在 contracts
目录下,添加一个新合约 InfoManager.sol
, 代码如下:
pragma solidity >0.4.21;
import "./Storage.sol";
contract InfoManager {
Storage private _dataStore;
uint private _lastAdded;
constructor(Storage dataStore) public {
_dataStore = dataStore;
}
function addData(string key, string value) public {
require((now - 1 days) > _lastAdded);
_dataStore.addData(key, value);
}
}
可以看出,InfoManager
依赖 Storage
合约,不仅如此InfoManager的构造函数还叙需要 Storage
合约作为参数。
重新修改一下 2_deploy_contracts.js
让它可以完成 InfoManager
的部署:
var Storage = artifacts.require("./Storage.sol");
var InfoManager = artifacts.require("./InfoManager.sol");
module.exports = function(deployer) {
// 部署 Storage
deployer.deploy(Storage)
// 等待、直到合约部署完成
.then(() => Storage.deployed())
// 传递 Storage 合约地址,部署 InfoManager 合约
.then(() => deployer.deploy(InfoManager, Storage.address));
}
部署的语法是:
...
deployer.deploy(`ContractName`, [`constructor params`]) // 返回一个 promise
...
因为 deploy(...)
返回一个 promise, 我们可以按自己喜欢的方式处理它,不要注意的是在部署文件里,还不支持 async
。
我们还可以在部署合约后自定义调用函数。例如,迁移还可以这样:
deployer.deploy(Storage)
.then(() => Storage.deployed())
.then((instance) => {
instance.addData("Hello", "world")
}).then(() => deployer.deploy(InfoManager, Storage.address));
这样在部署 InfoManager
之前,先给Storage
添加一条数据。
这个技巧很有用,因为有时相互依赖的合约可能需要在构造函数的之外写入。
还可以根据所处的网络不同,进行不同的部署。这对于在开发阶段使用模拟链的数据而在主网上线是使用已部署的主网合约作为输入参数到合约中都非常有用。
通过导出函数 module.exports
扩展一个参数 network
:
module.exports = function(deployer, network) {
if (network == "live") {
// do one thing
} else if (network == "development") {
// do other thing
}
}
module.exports
默认函数还可以公开一个账号参数,这些账号是以太坊节点或钱包provider “暴露” 出来的,看看下面的例子:
module.exports = function(deployer, network, accounts) {
var defaultAccount;
if (network == "live") {
defaultAccount = accounts[0]
} else {
defaultAccount = accounts[1]
}
}
你还可以通过 deployer.link(...)
连接已经存在(部署)的库:
...
deployer.deploy(MyLibrary);
deployer.link(MyLibrary, MyContract);
deployer.deploy(MyContract);
...
通过使用上面的这些技术,可以把大部分区块链部署工作自动化,并减少开发去中心化应用程序涉及的大量重复工作。
参考文章:
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!