本期《区块链100讲》我们将介绍一个更简单的部署智能合约的方法: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

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">$ npm install -g truffle@3.4.11</pre>
复制代码

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

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">$ truffle
Truffle v3.4.11 - a development framework for EthereumUsage: truffle <command> [options]Commands:
init     Initialize new Ethereum project with example contracts and tests
compile   Compile contract source files
migrate   Run migrations to deploy contracts
deploy   (alias for migrate)
build     Execute build pipeline (if configuration present)
test     Run Mocha and Solidity tests
console   Run a console with contract abstractions and commands available
create   Helper to create new contracts, migrations and tests
install   Install a package from the Ethereum Package Registry
publish   Publish a package to the Ethereum Package Registry
networks Show addresses for deployed contracts on each network
watch     Watch filesystem for changes and rebuild the project automatically
serve     Serve the build directory on localhost and watch for changes
exec     Execute a JS module within this Truffle environment
unbox     Unbox Truffle project
version   Show version number and exitSee more at http://truffleframework.com/docs</pre>
复制代码

3

创建并初始化项目

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">$ mkdir myproject$ cd myproject$ truffle init</pre>
复制代码

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

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">myproject
├── contracts
│   ├── ConvertLib.sol
│   ├── MetaCoin.sol
│   └── Migrations.sol
├── migrations
│   ├── 1_initial_migration.js
│   └── 2_deploy_contracts.js
├── test
│   ├── TestMetacoin.sol
│   └── metacoin.js
└── truffle.js</pre>
复制代码

contracts/ - 存放我们编写的合约。

migrations/ - 存放迁移部署脚本。

test/ - 存放合约测试脚本

truffle.js - Truffle的配置文件

truffle init会给我们创建一个名叫MetaCoin的代币应用。我们将这个默认的应用删除,我们将编写自己的合约。

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">$ cd myproject# 删除代币合约$ rm contracts/ConvertLib.sol$ rm contracts/MetaCoin.sol# 删除迁移脚本$ rm migrations/2_deploy_contracts.js# 删除测试脚本$ rm test/TestMetacoin.sol$ rm test/metacoin.js</pre>
复制代码

4

创建合约

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

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">$ cd contracts/
$ touch Storage.sol</pre>
复制代码

Storage.sol合约的内容如下:

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">pragmasolidity^0.4.8;contractStorage{uint256storedData;functionset(uint256data) {
storedData=data;
}functionget() constantreturns(uint256) {
returnstoredData;
}
}</pre>
复制代码

5

编译合约

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

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">$ truffle compileCompiling ./contracts/Migrations.sol...
Compiling ./contracts/Storage.sol...
Writing artifacts to ./build/contracts</pre>
复制代码

从控制台的输出中,我们可以看到合约编译后的文件(artifacts)会写入./build/contracts目录中,这些合约编译后的文件对于Truffle框架能否正常工作至关重要。请不要手动修改这些文件,因为即使修改了,再次执行编译命令时又会被覆盖掉。

Truffle默认只编译自上次编译后被修改过的合约,目的是为了减少不必要的编译。如果你想编译全部合约 ,可以使用--all选项。

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">$ truffle compile --all</pre>
复制代码

合约编译完成后,我们需要部署Storage.sol合约,在truffle中部署合约需要用到迁移脚本。下面我们进入migrations目录中为Storage合约创建一个迁移脚本。

6

迁移脚本

迁移脚本是由一些Javascript文件组成,用来帮助你把合约发布到以太坊网络中。之所以需要迁移脚本是因为你的部署需求会随着时间改变。随着你的项目的发展,你可以创建新的迁移脚本把这些变化的合约部署到区块链上。之前你运行的迁移历史记录,会被一个特殊的Migrations.sol合约记录在区块链上,后面将对Migrations.sol合约进行详细介绍。

移脚本的命名规则:文件名以数字开头,一个描述性的后缀结尾。数字前缀是必须的,用于记录移植是否成功。后缀仅是为了提高可读性,以方便理解。

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">$ cd migrations
$ touch 2_storage_migration.js</pre>
复制代码

文件:2_storage_migration.js

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">varStorage=artifacts.require("Storage");
module.exports=function(deployer) {deployer.deploy(Storage);
};</pre>
复制代码

1. artifacts.require()

在迁移脚本开头,我们通过artifacts.require()方法告诉truffle我们将要与那个合约交互。这个方法类似于NodeJs中的require,但在这里,它返回的是一个合约抽象,我们可以在我们的迁移脚本的其余部分中使用这个合约抽象。artifacts.require()中使用的名字不是必须与合约源文件的文件名相同,相反,它应该与在合约源代码中定义的合约类的名称相同。

2. module .exports

在迁移脚本最后,我们通过module.exports到处一个函数,被迁移脚本导出的函数都应该接受一个 deployer 对象作为其第一个参数。deployer对象中的辅助函数在部署过程中提供了一种清晰的语法,用于部署智能合约,以及执行一些常见的任务,比如把发布后的对象保存下来供以后使用。这个 deployer 对象是部署任务的主接口,它的API在本文后面有讲解。

好了,我们的一切准备工作都做好了,接下来我们就可以把Storage.sol部署到我们的区块链上了,在上一章中我们把智能合约部署到geth私有链中,这次我们将把智能合约部署到Testrpc环境中。

如果你还没有安装Testrpc 那么先执行下面的安装命令:

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">$ npm install -g ethereumjs-testrpc</pre>
复制代码

启动testrpc

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">$ testrpcEthereumJS TestRPC v4.0.1 (ganache-core: 1.0.1)Available Accounts
==================
(0) 0x9d02c028e7dd237213bd181cdb55f98da9ed8e64
(1) 0x0d157f5d9da61893093ba39fad87d08e6c55fc41
(2) 0xd69f903709b80f4ff9b338c725135ff8b7a55b94
(3) 0x5174fb1b516e75428cc1c9fa62e6df3b8313d280
(4) 0xafa676d65111e5ea1790981ca7ccbcf4e1fd37c2
(5) 0x454f7d978cdc588b0932614e512d8570b376aa17
(6) 0x4f8bd67351a54785534133a4b299e4fe402b3438
(7) 0x02d039305aa3628ab7f0cff7f9d7e4e90ff2a331
(8) 0xeace265c6f9153b66e2ce49a34d7a8f39a9223f1
(9) 0xcf396939dc0e067c0c95b5e11808903de62aa11aPrivate Keys
==================
(0) 95b6dac199323d4246ce8f13277b85fa94918b17f1271f1b3bee9a2b8e653118
(1) 3567fe613afe8e122cc785b8331907fd21e050eb3144203576d58d37026c9f21
(2) 67eacb4cf7ef07171443e29101cfcb369f7bee2b1ae4c6145c20719e88e7458d
(3) e6ea7cbbcb4e4014738dcdb093f0a8be9fee67c189e18e5f1baf384e05cbe525
(4) b151fbd620ee6a7fea833c8017b181a431d49a9c7e2a777d1ba5e6127b39f909
(5) 6765223734c785fbedce37e16786f7ab79fdd697933e27b4663bd72e5dd29b01
(6) 2d3ade35f2ecec67834fa2b08ab8935d0b4d8b704c41cef8ced9fa24475d7ee4
(7) 385edb489851649f6200621b4338121a7eebead8c05d5f50c036da59b8695a9a
(8) a412e30d941f9015d58bf27f335e90639e1b67cea9c3442ff64dbec58733ba04
(9) e0fd135471034fb5b2e6f494ad3947be012f15d2d08f26985f9bcc52e47e003f
复制代码

HD Wallet

Mnemonic:     million remember shell basket verify because image mobile extra novel rival purchase
Base HD Path: m/44'/60'/0'/0/{account_index}Listening on localhost:8545</pre>
复制代码

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

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">$ truffle migrateUsing network 'development'.Running migration: 1_initial_migration.js
Deploying Migrations...
... 0x9660bd2dad09d2417ab2d0d7931395d4425c857e09dca4cba37850229ea12004
Migrations: 0xe78a0f7e598cc8b0bb87894b0f60dd2a88d6a8ab
Saving successful migration to network...
... 0xbb4949bdd34ff8085babe7e8624b67834f79b8d287b131c79000be7163697932
Saving artifacts...
Running migration: 2_deploy_Storage.js
Deploying Storage...
... 0x33448d46fd119b46b9a49ba6550becee7266f9f02ad48561afca93bf4e13e912
Storage: 0xcfeb869f69431e42cdb54a4f4f105c19c080a601
Saving successful migration to network...
... 0x8e1f6cf2cf9221b6242de54995c6e71e0ae69781a6b4b587a98e3b3ae5acd047
Saving artifacts...
</pre>
复制代码

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

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">$ truffle migrate --reset</pre>
复制代码

3. 初始化迁移合约

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

文件名:contracts/Migration.sol

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">pragmasolidity^0.4.4;contractMigrations{addresspublicowner;uintpubliclast_completed_migration;modifierrestricted() {if(msg.sender==owner) _;
}functionMigrations() {owner=msg.sender;
}functionsetCompleted(uintcompleted) restricted{last_completed_migration=completed;
}functionupgrade(addressnew_address) restricted{Migrationsupgraded=Migrations(new_address);upgraded.setCompleted(last_completed_migration);
}
}</pre>
复制代码

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

文件名:migrations/1_initial_migrations.js

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">varMigrations=artifacts.require("./Migrations.sol");module.exports=function(deployer) {// Deploy the Migrations contract as our only taskdeployer.deploy(Migrations);
};</pre>
复制代码

要部署其他合约,你可以递增数字编号前缀来创建新的迁移脚本。

4. 部署器(deployer)

你的迁移脚本会使用这deployer对象来组织部署任务。deployer对象会同步执行部署任务,因此你可以按顺序编写部署任务。

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">// 先部署A,再部署B
deployer.deploy(A);
deployer.deploy(B);</pre>
复制代码

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

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">// 部署A, 然后部署 B, 把A 部署后的地址传给B
deployer.deploy(A).then( function () {
return deployer.deploy(B, A.address);
});</pre>
复制代码

5. deployer API

deployer对象包含许多方法,可以用来简化你的迁移工作。

(1) deployer.deploy(CONTRACT, ARGS…, OPTIONS)

这个API是用来部署合约的,contract参数传入需要部署的合约名字,args参数传入合约的构造函数需要的参数,options是一个可选参数它的值是{overwrite: true/false}, 如果 overwrite 被设置成 false, 那么当这个合约之前已经部署过了,这个deployer就不会再部署这个合约,这在当一个合约的依赖是由一个外部合约地址提供的情况下是有用的。

为了快速进行部署多个合约,你可以向deployer.deploy(.....)函数中传入一个或多个数组。

例子:

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">// 部署单个合约,不带任何构造参数
deployer.deploy(A);
// 部署单个合约带有构造参数
deployer.deploy(A, arg1, arg2, ...);
// 部署多个合约,一些带构造参数,一些不带构造参数.
// 比写3次 `deployer.deploy()` 语句更快, 因为deployer可以把所有的合约部署都一次性打包提交
deployer.deploy([
[A, arg1, arg2, ...],
B,
[C, arg1]
]);
// 外部依赖的例子:
//
// overwrite: false 表示,如果 SomeDependency 合约之前已经被部署过,那么不在重新部署,直接使用之前已部署好的地址
// 如果我们的合约要运行在自己的测试链上,或者将要运行的链上没有SomeDependency合约,
// 那么把overwrite: false改成overwrite: true,表示不在检查之前SomeDependency有没有部署过,一律覆盖部署。
deployer.deploy(SomeDependency, {overwrite:  false});</pre>
复制代码

(2) deployer.link(LIBRARY, DESTINATIONS)

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

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">// 部署库LibA,然后把LibA 链接到合约B,然后部署合约B.
deployer.deploy(LibA);
deployer.link(LibA, B);
deployer.deploy(B);//库LibA链接到多个合约
deployer.link(LibA, [B, C, D]);</pre>
复制代码

(3) deployer.then(function() {...})

在迁移过程中使用它调用特定合约的函数来部署新的合约,为已部署的合约做一些初始化工作等。

例子:

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">vara, b;
deployer.then( function() {// 部署合约A的一个新版本到网络上
returnA.new();
}).then( function(instance) {
a=instance;// 获取已部署的合约B的实例returnB.deployed();
}).then( function(instance) {
b=instance;// 使用合约B的setA()方法设置A的地址的新实例.
returnb.setA(a.address);
});</pre>
复制代码

(4) 网络相关

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

文件:truffle.js

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">module.exports = {
networks: {development: {host: "localhost",port: 8545,network_id: "*" // Match any network id}
}
};</pre>
复制代码

例子:

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">module.exports=function(deployer, network) {if(network=="live") {// 当不在"live"的网络上的时候,做一些特定的操作.
} else{// 当在的时候,做一些其他的操作.
}
}</pre>
复制代码

(5) 可用的账户

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

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">module.exports=function(deployer, network, accounts) {//在你的迁移脚本中使用账户
}</pre>
复制代码

7

合约交互

以太坊中将向以太坊网络写入数据和从以太坊网络中读取数据这两种操作做了区分。一般来说,写数据被称为交易(transaction),而读取数据称为调用(call)。交易和调用的处理方式非常不同,并且具有以下特征。

1、交易(transaction)

交易会从根本上改变了网络的状态。简单的交易有:发送以太币到另一个账户。复杂的交易有:调用一个合约的函数,向网络中部署一个合约。交易的显著特征是:

交易可以写入或修改数据;

交易花费以太币运行,就是我们所说的gas;

交易需要时间处理。

当你通过交易调用合约的函数时,我们将无法立即获得智能合约的返回值,因为该交易当前只是被发送,离被打包、执行还有一段时间。通常,通过交易执行的函数将不会立刻返回值,它们将返回一个交易ID。所以总结一下,一个交易一般有如下特征:

消耗gas(以太币)

更改网络的状态

不会立即处理

不会立刻返回一个返回值(只有一个交易ID)

2、 调用(CALL)

另一方面,调用则完全不一样。调用可以在网络上执行代码,但不会永久更改数据。调用可以免费运行,不需要花费gas。调用的显著特征是:调用是用来读取数据。当你通过调用执行合约函数时,你将立即收到返回值。总而言之,调用是:

是免费的(不消耗gas)

不会更改网络的状态

会被立即处理

会立刻返回一个值

决定使用交易还是调用,依据很简单:要读取数据还是写入数据。

3、 合约抽象

合约抽象是Javascript和以太坊合约交互的中间层粘合剂。简而言之,合约抽象帮我们封装好了代码,它可以让你和合约之间的交互变得简单,从而让你不必关心合约调用细节。Truffle通过truffle-contract模块来使用自己的合约抽象。合约抽象中的函数和我们合约中的函数是一样的。

为了使用合约抽象和合约交互,我们需要通过npm安装truffle-contract模块

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">$ cd myproject
$ npm init -y
$ npm install --save truffle-contract@3.0.1
$ npm install --save web3@0.20.0</pre>
复制代码

4、 与合约交互

(1)Call 方式交互

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

文件:call.js

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">varWeb3=require("web3");varcontract=require("truffle-contract");
vardata=require("../build/contracts/Storage.json");// 返回合约抽象
varStorage=contract(data);varprovider=newWeb3.providers.HttpProvider("http://localhost:8545");
Storage.setProvider(provider);// 通过合约抽象与合约交互
Storage.deployed().then(function(instance) {returninstance.get.call(); // call 方式调用合约
}).then(result=>{console.info(result.toString());// return 0
}).catch(err=>{// 报错了!在这里处理异常信息
});</pre>
复制代码

注意:

我们必须明确地调用.call()函数,告诉Ethereum网络知道我们不会修改区块链上的数据。

当调用成功是,我们会收到一个返回值,而不是交易ID。

(2)Transaction 方式交互

接下来我们以transaction方式给Storage.sol合约中storedData变量赋值为42,

文件:transaction.js

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">varWeb3=require("web3");varcontract=require("truffle-contract");
vardata=require("../build/contracts/Storage.json");// 返回合约抽象
varStorage=contract(data);varprovider=newWeb3.providers.HttpProvider("http://localhost:8545");
Storage.setProvider(provider);varstorageInstance;
Storage.deployed().then(function(instance) {storageInstance=instance;//以transaction方式与合约交互returnstorageInstance.set(42,{from:Storage.web3.eth.accounts[0]});
}).then(result=>{
// result 是一个对象,它包含下面这些值://// result.tx     => 交易hash,字符型// result.logs   => 在交易调用中触发的事件,数组类型// result.receipt => 交易的接收对象,里面包含已使用的gas 数量console.info(result.tx);//返回交易ID
}).then(()=>{// 调用Storage get 方法returnstorageInstance.get.call();
}).then(result=>{console.info(result.toString());// 返回 42 ,说明我们之前的调用成功了!
}).catch(err=>{// 报错了!在这里处理异常信息
});</pre>
复制代码

上面的代码有一些需要说明的地方:

我们直接调用这合约抽象的 set 方法。 默认情况下,这个操作会向区块链网络中发送一笔交易。也可以显式调用storageInstance.set.sendTransaction(42,{from:Storage.web3.eth.accounts[0]}),表明是以transaction方式交互

当这个交易成功发出后,回调函数只有在交易被成功打包处理后才会激活,这省去了你自己写判断语句检查交易状态的麻烦。

我们传递了一个对象给set函数的第二个参数。注意:在我们的Storage.sol合约代码中set函数并没有第三个参数,这第三个参数是合约抽象API里的。在合约抽象的所有函数中,你都可以向它们传入一个对象作为最后一个参数,在这个对象中你可以写入一些有关交易细节,在这个例子中,我们在对象中写入from字段,以确保这个交易是来自 web3.eth.accounts[0]。

5、 添加一个新合约到网络

在上面的所有例子中,我们使用的是一个已部署好的合约抽象,我们可以使用合约抽象的.new()函数来部署自己的合约。

文件:new.js

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">varWeb3=require("web3");varcontract=require("truffle-contract");
vardata=require("../build/contracts/Storage.json");// 返回合约抽象
varStorage=contract(data);varprovider=newWeb3.providers.HttpProvider("http://localhost:8545");
Storage.setProvider(provider);varstorageInstance;// new 部署新的合约
Storage.new({from:Storage.web3.eth.accounts[0],gas:1000000}).then(function(instance) {storageInstance=instance;// 输出新合约的地址console.log(instance.address); // 0xfc628dd79137395f3c9744e33b1c5de554d94882
}).catch((err) =>{console.info(err)// 报错了!在这里处理异常信息
});</pre>
复制代码

6、 使用现有合约地址

如果你已经有一个合约地址,你可以通过这个地址创建一个新的合约抽象。

<pre style="margin: 0px 0px 10px; padding: 9.5px; border-radius: 4px; background: rgb(245, 245, 245); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; max-width: 100%; color: rgb(51, 51, 51); letter-spacing: 0.544px; text-align: justify; overflow: auto; font-family: Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.42857; word-break: break-all; border-width: 1px; border-style: solid; border-color: rgb(204, 204, 204); box-sizing: border-box !important; word-wrap: break-word !important;">varWeb3=require("web3");varcontract=require("truffle-contract");
vardata=require("../build/contracts/Storage.json");// 返回合约抽象
varStorage=contract(data);varprovider=newWeb3.providers.HttpProvider("http://localhost:8545");
Storage.se                    </pre>
复制代码

内容来源:区块链兄弟

原文来源:《区块链开发实战-以太坊关键技术与案例分析》-机械工业出版社

作者:吴寿鹤

《区块链100讲》专栏策划及内容编辑:HiBlock区块链社区Cynthia

如需转载,需申请并注明专栏及原文出处。

线上课程推荐

【线上课程】4节课8小时培训,《白话区块链》作者蒋勇教你快速掌握区块链智能合约开发

线下活动推荐

技术工坊|(分享+实践)1天学会区块链Dapp+代币开发(西安)

技术沙龙|利用防篡改可追溯特性,探寻区块链在政府及实体企业的应用方向(成都)

区块链100讲:Truffle——一个更简单的部署智能合约的方法相关推荐

  1. 区块链100讲:据说,80%的人都搞不懂哈希算法

    2019独角兽企业重金招聘Python工程师标准>>> 前面的<区块链100讲>介绍了区块链.算力.挖矿等,几乎每一讲都会提到一个词哈希(Hashing).聊到区块链的时 ...

  2. 区块链100讲:不做码农做矿工,该怎么和爹妈解释

    说起区块链和比特币的时候都会提到一个词"挖矿",还有个角色叫"矿工",等等,区块链不是属于技术圈吗?怎么和挖矿扯上关系了?只听说过管IT圈儿的人叫码农,怎么还有 ...

  3. 区块链100讲:带你走进EOS的存储系统

    2019独角兽企业重金招聘Python工程师标准>>> 1 海量数据 在一个完全去中心化的区块链系统中,每个节点如果想验证某一笔交易是否正确,则必须下载完整的区块链数据,随着时间的推 ...

  4. 区块链100讲:梅克尔树保障区块链数据不可篡改,想换根就要砍树!

    2019独角兽企业重金招聘Python工程师标准>>> 区块链100讲上期我们讲了哈希算法和公开密钥算法,说到哈希算法提到了一个名词"Merkle tree",梅 ...

  5. 区块链100讲:V神·以太坊上的分片

    五月初,以太坊创始人"V神"Vitalik Buterin表示,以太坊的内部扩展解决方案--分片已经接近完成.以太坊分片旨在将以太坊分成几个并发网络,从而使整个网络更加高效地扩展, ...

  6. 区块链100讲:从村里的账本来看什么是区块链

    2019独角兽企业重金招聘Python工程师标准>>> 很久以前就有个想法,把区块链的技术和概念整理成一个体系化的知识图谱,方便大家查阅和学习,大话已说出去很久,却一直没有践行(pa ...

  7. 区块链100讲:能够证明你是你的数字签名和多重签名

    随着区块链相关技术的创新和突破,很多有形或无形资产都将实现去中心化,数字资产将无处不在.要保护数字出版物版权,实现去中心化,解决业界多年来版权保护不力的难题.无论数字资产,还是数字出版版权,都是有明确 ...

  8. 区块链100讲:Hyperledger Fabric 区块链多机部署

    区块链技术可以应用在很多领域,未来最有可能先在这些领域落地. 区块链技术是利用块链式数据结构来验证与存储数据.利用分布式节点共识算法来生成和更新数据.利用密码学的方式保证数据传输和访问的安全.利用由自 ...

  9. 区块链100讲:区块链的TPS性能

    2019独角兽企业重金招聘Python工程师标准>>> 最近在做一个区块链系统的底层设计,有一个绕不开的问题是,就是我的系统能提供多少个tps.为此,我收集了一些现有区块链(当然也包 ...

最新文章

  1. 面试题:css之品字布局?
  2. OpenGL material light材质灯光的实例
  3. 遇到的JDBC的一个问题
  4. DM9000调试记录
  5. Lethean结点搭建
  6. 计算器小程序java课程设计,java课程设计----计算器小程序报告.doc
  7. windows活动目录与网络系列(1)
  8. u-boot 2016.05 添加自己的board 以及config.h uboot移植
  9. Eclipse超级好看的主题,极力推荐
  10. swfobject的使用
  11. windows 10 连接android手机助手,手把手教你Win10手机助手怎么用
  12. Android:根据阳历日期获取农历日期
  13. vm8网卡原理及应用
  14. ncbi和ensembl上的序列下载
  15. Dline,一款让你爱不释手的去中心化社交应用
  16. 排队论和对策论(博弈论)
  17. 【操作系统】进程、线程、协程和并发、并行
  18. 使用二手书App的心得
  19. 如何将计算机基础拷到u盘上,怎么把电脑上的CAD拷贝到u盘里
  20. Django微信支付

热门文章

  1. 详解线性反馈移位寄存器(LFSR)
  2. TinyOS 学习第一周-Ubuntu-10.10下安装TinyOS-2.1.1
  3. 好看的table css样式
  4. Java poi导入合并单元格的excel数据【最完整】附pom文件和excel截图
  5. 利用Matlab考察数据的边缘正态性 作Q_Q图
  6. 如何破坏Excel文件,让其显示文件已损坏方法
  7. opencv、opencv_contrib及cuda联合编译详细教程
  8. 计算机网络复习04——网络层
  9. java cos90,Java Math.cosh() 方法
  10. 毕业生如何写简历的内容