区块链入门到实战(27)之以太坊(Ethereum) – 智能合约开发
智能合约的优点
与传统合同相比,智能合约有一些显著优点:
- 不需要中间人
- 费用低
- 代码就是规则
- 区块链网络中有多个备份,不用担心丢失
- 避免人工错误
- 无需信任,就可履行协议
- 匿名履行协议
以太坊(Ethereum) – 智能合约开发概述
支持智能合约的区块链
虽然以太坊(Ethereum)是最流行支持智能合约的区块链平台,但它并不是唯一支持智能合约的平台。
超级账本(Hyperledger) 是Linux基金会于2015年发起的推进区块链数字技术和交易验证的开源项目。通过创建分布式账本的公开标准,实现虚拟和数字形式的价值交换,例如资产合约、能源交易、结婚证书、能够安全和高效低成本的进行追踪和交易。
另外,还有其他很多区块链平台支持智能合约,可以参考相关资料。
以太坊(Ethereum)智能合约开发工具
通常,开发智能合约需要用到工具:
- Mist – 以太坊节点/钱包。
- Truffle 框架 – 流行的以太坊开发框架,内置了智能合约编译、链接、部署等功能。
- Metamask – Chrome插件方式的以太坊节点/钱包。
- Remix – Remix是一个基于web浏览器的智能合约开发环境(IDE)。
以太坊(Ethereum)智能合约开发语言
目前主要的智能合约开发语言是 Solidity语言,是一种开发以太坊智能合约的静态高级语言,语法类似于JavaScript。
还有另外一些智能合约开发语言:
等等。
以太坊(Ethereum) – 智能合约开发环境搭建
为了构建开发智能合约或者dApp,我们需要安装以下模块:
- Node 与 NPM
- Truffle 框架
- Ganache
- Metamask
- VScode 与 Solidity插件
Node 与 NPM
Truffle 框架依赖Node,需要使用npm安装。
首先需要安装node,npm会同时安装,下载node,按提示安装。
安装完后,可以验证一下node版本:
$ node -v
Truffle 框架
Truffle框架是流行的以太坊开发框架,内置了智能合约编译、链接、部署等功能。
使用npm安装Truffle框架:
$ npm install -g truffle
验证truffle安装:
$ truffle --version
Truffle v5.0.35 - a development framework for Ethereum
...
Ganache
在实际的以太坊网络上测试、部署Dapp或智能合约,需要消耗Gas。Ganache可以在本地创建区块链网络来测试我们的程序。
可以从Truffle Framework网站下载Ganache来安装。它将创建一个本地区块链网络,给我们分配10个外部账号,每个帐户都有100个假的以太币。
Metamask
Metamask是一个Chrome插件形式的以太坊节点/钱包。
我们可以使用Metamask连接到本地区块链网络或实际的以太坊网络,并与我们的智能合约交互。
要安装Metamask,请在谷歌Chrome web store中搜索Metamask Chrome插件并安装。一旦安装,请确保打开启用按钮。安装后,你会在Chrome浏览器的右上角看到狐狸图标。
VS code 与 Solidity插件
推荐使用vs code编辑器编写solidity代码,vs code可以安装一下Solidity插件,以便支持语法高亮功能。
以太坊(Ethereum) – Ganache本地区块链
我们安装了Ganache。现在启动Ganache,创建本地的以太坊区块链网络。
主界面
本地区块链可以模拟公共区块链,开发人员可以在本地区块链上测试智能合约。打开Ganache,界面如下图所示:
本地区块链缺省有10个外部账号,每个账号都有100个假的以太币,这些可以通过设置改变。
Ganache界面中有下面几个主要页面:
- ACCOUNTS – 账号页面,这显示了自动生成的所有帐户及其余额。
- BLOCKS – 区块页面,显示了在本地区块链网络上挖掘的每个区块,及其Gas成本和包含的交易。
- TRANSACTIONS – 交易页面,列出了在本地区块链上发生的所有交易。
- CONTRACS – 合约页面
- EVENTS – 事件页面
- LOGS – 日志页面
搜索区块或交易
界面顶部的搜索栏,可以让你搜索本地区块链网络上的区块或交易。
设置
可以通过设置来定制Ganache的一些功能,单击主界面右上角的图标进入设置页面。
以下是一些主要设置:
- SERVER – 服务器设置页面,管理关于网络连接的详细信息,比如网络id、端口、主机名和自动挖掘状态。
- ACCOUNTS & KEYS – 帐户和密钥页,设置自动生成的帐户数量及其余额,缺省10个账号,每个账号余额是100 ether。
- CHAIN – 链页,让你为网络设置Gas限制和Gas价格。
- 高级设置 – 日志选项设置,比如保存日志文件和配置详细输出的能力。
请注意,在更改了新的设置之后,必须Restart
(设置页面右上角)才能生效。
以太坊(Ethereum) – 开发智能合约
我们将使用truffle创建一个智能合约项目,该智能合约的功能是可以获取值和设置值。
1. 初始化项目
首先创建项目目录:
$ mkdir mydapp
$ cd mydapp
然后使用truffle init
初始化项目,将生成项目模板文件:
$ truffle init
我们可以查看一下生成的项目目录:
G:\qikegu\ethereum\mydapp>tree /f
卷 数据 的文件夹 PATH 列表
卷序列号为 0C52-9CF4
G:.
│ truffle-config.js
│
├─contracts
│ Migrations.sol
│
├─migrations
│ 1_initial_migration.js
│
└─test
- contracts 目录 智能合约源文件目录,现在已经有了一个
Migrations.sol
源文件,功能是迁移/部署/升级智能合约。 - migrations 目录 迁移文件目录,迁移文件都是javascript脚本,帮助我们把智能合约部署到以太坊。
- test 目录 测试代码目录。
- truffle-config.js 文件 Truffle项目配置文件,例如可以在里面配置网络。
添加package.json文件
package.json
是npm用来管理包的配置文件,在项目根目录下创建此文件,内容如下:
{
"name": "ethereum-demo",
"version": "1.0.0",
"description": "以太坊demo",
"main": "truffle-config.js",
"directories": {
"test": "test"
},
"scripts": {
"dev": "lite-server",
"test": "echo \"Error: no test specified\" && sexit 1"
},
"author": "kevinhwu@qikegu.com",
"license": "ISC",
"devDependencies": {
"@truffle/contract": "^4.0.33",
"dotenv": "^8.1.0",
"lite-server": "^2.5.4",
"truffle-hdwallet-provider": "^1.0.17"
}
}
关于依赖的包,可以在后面章节中,用到时逐个安装。
2. 添加智能合约源文件
在contracts 目录中创建一个新文件MyContract.sol
,内容如下所示:
// 声明solidity版本
pragma solidity ^0.5.0; // 声明智能合约MyContract,合约的所有代码都包含在花括号中。
contract MyContract { // 声明一个名为value的状态变量
string value; // 合约构造函数,每当将合约部署到网络时都会调用它。
// 此函数具有public函数修饰符,以确保它对公共接口可用。
// 在这个函数中,我们将公共变量value的值设置为“myValue”。
constructor() public {
value = "myValue";
} // 本函数读取值状态变量的值。可见性设置为public,以便外部帐户可以访问它。
// 它还包含view修饰符并指定一个字符串返回值。
function get() public view returns(string memory ) {
return value;
} // 本函数设置值状态变量的值。可见性设置为public,以便外部帐户可以访问它。
function set(string memory _value) public {
value = _value;
}
}
这个智能合约的功能是可以获取值和设置值。
3. 编译项目
现在让我们编译项目:
项目目录下执行命令:
$ truffle compile
等编译完成,可以看到多了一个build
目录,该目录下生成了新文件:./build/contract/MyContract.json
这个文件是智能合约ABI文件,代表“抽象二进制接口”。这个文件有很多作用,其中2个重要作用:
- 作为可在Ethereum虚拟机(EVM)上运行的可执行文件
- 包含智能合约函数的JSON表示,以便外部客户端可以调用这些函数
以太坊(Ethereum) – 部署智能合约到Ganache
接下来,我们将编译好的智能合约部署到本地的Ganache区块链网络。步骤如下:
- 更新项目的配置文件,修改网络配置连接到本地区块链网络(Ganache)。
- 创建迁移脚本,告诉Truffle如何部署智能合约。
- 运行新创建的迁移脚本,部署智能合约。
1. 更新配置文件
更新项目的配置文件,修改网络配置连接到本地区块链网络(Ganache)。
打开位于项目根目录下的truffle-config.js
文件,修改内容如下:
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 7545,
network_id: "*" // Match any network id
}
},
solc: {
optimizer: {
enabled: true,
runs: 200
}
}
}
这些网络配置,包括ip地址、端口等,应该与Ganache的网络配置匹配:
2. 创建迁移脚本
接下来,我们将在migrations
目录中创建迁移脚本,告诉Truffle如何部署智能合约,在该目录中创建文件2_deploy_contracts.js
。
注意,在migrations目录中所有文件都有编号,作用是让Truffle知道执行它们的顺序。
2_deploy_contracts.js
文件内容如下:
var MyContract = artifacts.require("./MyContract.sol"); module.exports = function(deployer) {
deployer.deploy(MyContract);
};
上面的代码中:
- 首先,
require
了创建的合约,并将其分配给一个名为“MyContract”的变量。 - 接着,将合约加入部署清单,运行迁移命令时合约将被部署。
3. 执行迁移命令
现在让我们从命令行执行迁移命令, 部署智能合约。
$ truffle migrate
执行详情如下:
G:\qikegu\ethereum\mydapp>truffle migrate Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile. Starting migrations...
======================
> Network name: 'development'
> Network id: 5777
> Block gas limit: 0x6691b7 1_initial_migration.js
====================== Deploying 'Migrations'
----------------------
> transaction hash: 0xe62fb8a27c9ccc894562fbd7a7797526ad9323ab67a44516ae342642bf4ffcc6
> Blocks: 0 Seconds: 0
> contract address: 0x168A7247B58786edd259502948f5Bf9449C863AD
> block number: 1
> block timestamp: 1568189958
> account: 0x29920e756f41F8e691aE0b12D417C19204371E91
> balance: 99.99477214
> gas used: 261393
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00522786 ETH > Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00522786 ETH 2_deploy_contracts.js
===================== Deploying 'MyContract'
----------------------
> transaction hash: 0xe9dcef6f70332e476684e8f93ab96969af53920555161054f1f4bcc6277116fb
> Blocks: 0 Seconds: 0
> contract address: 0x4D3CFaF8457CEA76c0409f989f9870115B4d2d82
> block number: 3
> block timestamp: 1568189959
> account: 0x29920e756f41F8e691aE0b12D417C19204371E91
> balance: 99.98804272
> gas used: 294448
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00588896 ETH > Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00588896 ETH Summary
=======
> Total deployments: 2
> Final cost: 0.01111682 ETH receipt:
{ transactionHash:
'0x83be6ef86fe542b3c94ae1dd5f2e04570c199d6b2e7997af60f3d91cda9259ec',
transactionIndex: 0,
blockHash:
'0x6e58c2c77b5998004b8a8c66760ca923814865307c69f1c779673cc2cbca06bc',
blockNumber: 5,
from: '0x29920e756f41f8e691ae0b12d417c19204371e91',
to: '0x4d3cfaf8457cea76c0409f989f9870115b4d2d82',
gasUsed: 33501,
cumulativeGasUsed: 33501,
contractAddress: null,
logs: [],
status: true,
logsBloom:
'0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
v: '0x1c',
r:
'0xdaf1578a7987ec5d4e7d25c4b66f570d97f880b783d3403b54fa7eb30b1ab836',
s:
'0x4024f2b26bab6277cc86da9727a9bccc1ba7832773b9c2781b265f8dd87df46f',
rawLogs: [] },
logs: [] }
可以看到,我们已经将智能合约成功部署到本地的Ganache区块链网络。
以太坊(Ethereum) – 使用 truffle console 访问智能合约
truffle console 是区块链开发人员的强大工具,这是一个命令行工具,可以在命令行中执行javascript代码,与智能合约进行交互。这对于开发智能合约非常有用。
我们已经成功地将智能合约部署到本地区块链网络,接下来我们将使用 truffle console 与智能合约进行交互。
启动 truffle console:
$ truffle console
进入控制台后,让我们获取已部署智能合约的一个实例,看看能否从该合约中读取value
值。从控制台运行以下代码:
MyContract.deployed().then((instance) => { app = instance } )
这里MyContract
是之前在迁移文件中创建的变量名称,使用deployed()
函数获取一个已部署合约的实例,并将其分配给promise
回调函数中的一个app
变量。
现在可以获取智能合约中的value
值:
app.get()
// => 'myValue'
给value
设置一个新值:
app.set('New Value')
重新获取智能合约中的value
值:
app.get()
// => 'New Value'
可以通过以下命令退出truffle console:
.exit
以太坊(Ethereum) – 智能合约测试(truffle test)
类似Java中JUnit单元测试工具,Trfuffle test可以帮助我们对智能合约项目进行白盒测试。
对于区块链项目,测试显得尤其重要,因为部署合约、迁移合约的成本都是相当高的,都要消耗Gas。
编写测试代码
现在让我们对前面章节中创建的智能合约,编写一些测试代码。整个测试过程模拟对智能合约MyContract
获取value
值、设置value
值的过程。
先确保MyContract
已经正确部署到Ganache本地区块链网络中。测试中将会用到Mocha测试框架,与Chai断言库,但Truffle已经集成了这些库。
测试代码用JavaScript编写,模拟与智能合约的交互,就像使用truffle console所做的那样。
在项目根目录下的test
目录中,添加测试脚本文件: MyContract.js
MyContract.js
中的测试代码:
// 首先,`require`合约并将其分配给一个变量`MyContract`
const MyContract = artifacts.require('./MyContract.sol'); // 调用“contract”函数,并在回调函数中编写所有测试
// 回调函数提供一个“accounts”变量,表示本地区块链上的所有帐户。
contract('MyContract', (accounts) => { // 第1个测试:调用get()函数,检查返回值,测试合约中value初始值是否是: 'myValue'
it('initializes with the correct value', async () => {
// 获取合约实例
const myContract = await MyContract.deployed()
const value = await myContract.get()
// 使用断言测试value的值
assert.equal(value, 'myValue')
}) // 第2个测试: 调用set()函数来设置value值,然后调用get()函数来确保更新了值
it('can update the value', async () => {
const myContract = await MyContract.deployed()
myContract.set('New Value');
const value = await myContract.get()
assert.equal(value, 'New Value')
})
})
代码说明,请见注释。
运行测试脚本
执行命令行运行测试:
$ truffle test
测试详情:
G:\qikegu\ethereum\mydapp>truffle test
Using network 'development'. Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile. Contract: MyContract
√ initializes with the correct value (76ms)
√ can update the value (78ms) 2 passing (188ms)
以太坊(Ethereum) – 连接公链
本篇介绍如何将Truffle项目连接到公共区块链网络。
到目前为止,我们连接的都是在本地区块链(Ganache),接下来将连接以太坊公链。
以太坊公链除了主网,还有多个测试网络。主网(Mainnet)是正式的以太坊网络,里面的以太币是真正有价值的,测试网络中的以太币没有价值,只用于测试。
我们最终目标是连接到主网,但先连接到测试网络Kovan,虽然本地区块链网络(Ganache)也能测试,但与公链还是有区别的。
连接到公链的步骤如下:
- 设置钱包来管理公链帐户
- 连接到以太坊节点
- 更新项目设置
- 访问以太坊节点
设置钱包
首先需要设置一个钱包,来管理我们的公链帐户。
简单起见,可以借用Ganache本地区块链钱包,由于区块链的工作原理,这个钱包在公共区块链和本地区块链上都是有效的。
打开Ganache,主界面上可以看到一个名为“MNEMONIC”的部分:
这是一个种子短语,用于构建由Ganache管理的钱包。我们可以使用这个种子短语加密重建钱包,来连接到公链。
复制这个值,保存到一个秘密文件,MNEMONIC是一个秘密值,需要保密。在项目根目录中创建一个.env
文件,保存MNEMONIC值,如下所示:
MNEMONIC="你的mnemonic"
连接以太坊节点
现在已经创建了钱包,下一步需要访问Ethereum节点,以便连接到公共区块链网络。
有几种方法可以做到这一点,可以使用Geth或Parity运行自己的Ethereum节点。但这需要从区块链下载大量数据并保持同步,很麻烦。
比较方便的方法是,使用Infura访问Ethereum节点。Infura是一个免费提供Ethereum节点的服务。
在Infura上注册账号,创建项目,在项目详情页上可以查看API KEY:
使用API KEY,就可以访问以太坊网络节点。
在.env
文件中添加Infura api key的配置:
INFURA_API_KEY="https://kovan.infura.io/v3/543526cd4d3846acbc3826484e934564"
MNEMONIC="你的mnemonic"
更新项目设置
接下来使用MNEMONIC
与INFURA_API_KEY
,更新项目的网络配置,以便连接到公共区块链网络。
修改truffle-config.js
文件:
// 导入dotenv库创用于读取`.env`文件中的设置
require('dotenv').config();
// 导入truffle-hdwallet-provider库重建钱包
const HDWalletProvider = require('truffle-hdwallet-provider'); module.exports = {
networks: {
development: {
host: "127.0.0.1", // Localhost (default: none)
port: 7545, // Standard Ethereum port (default: none)
network_id: "*", // Any network (default: none)
},
// Useful for deploying to a public network.
// NB: It's important to wrap the provider as a function.
kovan: {
provider: () => new HDWalletProvider(
process.env.MNEMONIC,
process.env.INFURA_API_KEY
),
gas: 5000000,
gasPrice: 25000000000,
network_id: 42
},
},
solc: {
optimizer: {
enabled: true,
runs: 200
}
}
}
可以看到,我们使用了.env
配置文件中的MNEMONIC
与INFURA_API_KEY
配置了kovan网络。
由于用到了dotenv与truffle-hdwallet-provider这2个库,我们需要先安装:
切换到项目目录,执行以下命令
npm install dotenv --save-dev
npm install truffle-hdwallet-provider --save-dev
注意 安装truffle-hdwallet-provider时,如果出现node-gyp相关的错误,可参考这里解决。
访问以太坊节点
使用truffle console
连接到公共区块链网络:
$ truffle console --network kovan
要验证连接,可以从区块链中读取一些数据,获取一些关于最新区块的信息,在控制台上执行:
web3.eth.getBlock('latest').then(console.log)
输出
{ author: '0x03801efb0efe2a25ede5dd3a003ae880c0292e4d',
difficulty: '',
extraData:
'0xde830206028f5061726974792d457468657265756d86312e33362e30826c69',
gasLimit: '0x7a1200',
gasUsed: '0x17d23',
hash:
'0xc7390c4f492c8c1da60608135fc9e05930123b645b39f221cba33d8b3c577b2a',
logsBloom:
'0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000080000000000000000000100000008000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400800000000000010000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000008000000',
receiptsRoot:
'0x3d05bb2ed4fcc90234eea6d840e7d0e3ce7f598a15e5314536b17bcd11c78b5b',
sealFields:
[ '0x84175e8801',
'0xb84155a8cdb108dccec1d314124058fa6f22e7400ee200db0a94b7b165e4c3454c1818cc05f815cb7ce48f7a88b8401515740311a3566d9cf079428d506a6daca50101' ],
sha3Uncles:
'0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347',
signature:
'55a8cdb108dccec1d314124058fa6f22e7400ee200db0a94b7b165e4c3454c1818cc05f815cb7ce48f7a88b8401515740311a3566d9cf079428d506a6daca50101',
size: 877,
stateRoot:
'0x03af5adce52a81ce5d332cddb9955e344214bff00859b78868116e1e839efdf7',
step: '',
timestamp: 1568284676,
totalDifficulty: '',
transactions:
[ '0xded7fed0842fd65ec808bc3652ec4175bc190acc11345c49c44b1fb5d954610f',
'0x7e9112a46fa3c07aad813ea86355b15eebb44023c040d198ee7d15d379bbc2be' ],
transactionsRoot:
'0x0dd10d90686dda2684bd0ba70d1c9e1d9a5302c30ca75eb2c5b07a7b6e4498b9',
uncles: [] }
可以看到,已经成功连接到了公链。
以太坊(Ethereum) – 部署智能合约到公链
现在,我们将智能合约部署到公链。步骤如下:
- 部署需要消耗Gas,获取测试以太币用于部署
- 部署智能合约
- 验证部署
获取测试以太币
部署需要消耗Gas,Gas需要支付以太币,我们部署到的是公链测试网Kovan,网络中的以太币没有市场价值。
可以从Kovan faucet Gitter聊天室获取测试用的伪以太币。只需把钱包地址发送出去,约5分钟内,有人会给你发测试用的伪以太币。
打开Ganache并复制列表中第一个帐户的地址(钱包地址),类似下面所示:
0x29920e756f41F8e691aE0b12D417C19204371E91
发送到聊天室内,稍等片刻,你的账号将收到一笔以太币。
部署智能合约
现在帐户里已经有了资金,可以进行部署了。
执行部署命令:
truffle migrate --network kovan
一旦部署完成,应该会看到部署成功的消息。
部署命令执行详情:
G:\qikegu\ethereum\mydapp>truffle migrate --network kovan Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile. Migrations dry-run (simulation)
===============================
> Network name: 'kovan-fork'
> Network id: 42
> Block gas limit: 0x7a1200 ... Starting migrations...
======================
> Network name: 'kovan'
> Network id: 42
> Block gas limit: 0x7a1200 1_initial_migration.js
====================== Deploying 'Migrations'
----------------------
> transaction hash: 0x7e30b5c716afed45888a9dd2d6af7e6f52a9fade0346e8ad7d0c268de508a26a
> Blocks: 2 Seconds: 9
> contract address: 0x168A7247B58786edd259502948f5Bf9449C863AD
> block number: 13447029
> block timestamp: 1568294312
> account: 0x29920e756f41F8e691aE0b12D417C19204371E91
> balance: 2.993465175
> gas used: 261393
> gas price: 25 gwei
> value sent: 0 ETH
> total cost: 0.006534825 ETH > Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.006534825 ETH 2_deploy_contracts.js
===================== Deploying 'MyContract'
----------------------
> transaction hash: 0xc1f7ec8fee1a23e3d08d0c9e9d6e15fef24feb8ba163e0071dccb1bb90cc0eca
> Blocks: 0 Seconds: 0
> contract address: 0x4D3CFaF8457CEA76c0409f989f9870115B4d2d82
> block number: 13447036
> block timestamp: 1568294340
> account: 0x29920e756f41F8e691aE0b12D417C19204371E91
> balance: 2.9850534
> gas used: 294448
> gas price: 25 gwei
> value sent: 0 ETH
> total cost: 0.0073612 ETH > Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.0073612 ETH Summary
=======
> Total deployments: 2
> Final cost: 0.013896025 ETH Summary
=======
> Total deployments: 2
> Final cost: 0.013896025 ETH
验证部署
现在打开truffle控制台,与kovan测试网络上的智能合约进行交互:
$ truffle console --network kovan
在控制台中执行:
truffle(kovan)> MyContract.deployed().then((c) => { contract = c })
然后:
truffle(kovan)> contract.get()
'myValue'
truffle(kovan)> contract.set("hello world")
{ tx:
'0x7bf63444f3a7bd70e981a7bd49228b1cf1a8c3754daf64c4c7765b8eee46bf37',
receipt:
{ blockHash:
'0xe03d0f43d85f4e41c18a90aa563ebda08899c6b9c38d0cd7779937046e2aed0c',
blockNumber: 13447763,
contractAddress: null,
cumulativeGasUsed: 33629,
from: '0x29920e756f41f8e691ae0b12d417c19204371e91',
gasUsed: 33629,
logs: [],
logsBloom:
'0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
root: null,
status: true,
to: '0x4d3cfaf8457cea76c0409f989f9870115b4d2d82',
transactionHash:
'0x7bf63444f3a7bd70e981a7bd49228b1cf1a8c3754daf64c4c7765b8eee46bf37',
transactionIndex: 0,
rawLogs: [] },
logs: [] }
truffle(kovan)> contract.get()
'hello world'
可以看到智能合约已经成功部署。
以太坊(Ethereum) – truffle脚本
Truffle包含一个脚本运行器,可对以太坊网络执行自定义脚本。
让我们创建一个脚本并执行。
在项目根目录下,创建script.js
文件,内容如下:
module.exports = function(callback) {
web3.eth.getBlock('latest').then(console.log)
}
该脚本将从Kovan测试网络获取最新区块的信息。
执行脚本:
truffle exec script.js --network kovan
输出
{ author: '0x596e8221a30bfe6e7eff67fee664a01c73ba3c56',
difficulty: '',
extraData:
'0xde830205058f5061726974792d457468657265756d86312e33362e30826c69',
gasLimit: '0x7a1200',
gasUsed: '0x5e61',
hash:
'0x225a1e0b13fd20396af60d049ce9bb94c2f3f7df06c7db260880b62c91997004',
logsBloom:
'0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
miner: '0x596e8221A30bFe6e7eFF67Fee664A01C73BA3C56',
number: 13448162,
parentHash:
'0x28d00fd7b66771130ed98de5073c7797ee293e7bee4b546793a4b79171555066',
receiptsRoot:
'0x44617b5733ee59bde159af08ffd6edae36e0964f1724c333f3d1bef0808dee15',
sealFields:
[ '0x84175e95d7',
'0xb8412ed900e67f4a72925fb3b495efb3f547411f40d26e972cc0e8b2cf26e40cf84a545e0328199d4880b79c62670129a7db12ac58234bee0866c6376b46ab99e8a200' ],
sha3Uncles:
'0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347',
signature:
'2ed900e67f4a72925fb3b495efb3f547411f40d26e972cc0e8b2cf26e40cf84a545e0328199d4880b79c62670129a7db12ac58234bee0866c6376b46ab99e8a200',
size: 797,
stateRoot:
'0xe1bbaacfb950361bec70f4ad53a2605e1ac1d2ff0bfd913fe063dc6c5f3252a0',
step: '',
timestamp: 1568298844,
totalDifficulty: '',
transactions:
[ '0xf1ae41eac6b32419bc62a6cde9cab4b4ca244899a3d49b4a2461bcf94f504176' ],
transactionsRoot:
'0xf08c8097ea946f84ce9594ce73648fc0f9f683adef105a5db00c5f1f15e61c2c',
uncles: [] }
下面的代码智能合约MyContract
中,读取value值,将script.js
脚本文件中的代码替换为:
const MyContract = artifacts.require("./MyContract.sol"); module.exports = async function(callback) {
const contract = await MyContract.deployed()
const value = await contract.get()
console.log("Value:", value)
}
执行脚本:
truffle exec script.js --network kovan
输出
Value: hello world
以太坊(Ethereum) – 让浏览器支持区块链(MetaMask)
大多数web浏览器目前都不支持连接到区块链网络,不过可以通过安装浏览器插件,来让浏览器支持区块链。
安装MetaMask
我们将为Chrome浏览器安装Metamask钱包插件(需翻墙)。
安装好后,确保插件的启用按钮打开,在浏览器的右上角会看到一个狐狸图标。
导入账号
把钱包账号从Ganache导入到Metamask中,这样我们就可以连接到区块链了。
打开Ganache主界面,如下图所示,复制MNEMONIC的值:
打开Metamask,选择通过Seed Phrase导入账号,把复制MNEMONIC的值,粘贴到Wallet Seed,如下图所示:
进入钱包
查看Kovan网络,可以看到里面有一些测试以太币余额。
现在我们的Chrome浏览器已经支持区块链了。
以太坊(Ethereum) – 智能合约前端页面
现在我们来开发智能合约的前端页面,让用户可以通过前端页面与智能合约交互。这个页面的主要功能是:
- 显示当前连接的帐户
- 读取智能合约中存储的value值
- 更新智能合约中存储的value值
页面大概的样子:
为开发前端页面,需要完成下面几项工作:
- 配置web服务器,用来部署页面
- 创建前端的h5、js文件
配置web服务器
首先,让我们来配置web服务器。服务器使用lite-server,安装lite-server:
$ npm install lite-server --save-dev
项目根目录下,创建lite-server的配置文件bs-config.json
,内容如下:
{
"server": {
"baseDir": [
"./src",
"./build/contracts"
],
"routes": {
"/vendor": "./node_modules"
}
}
}
baseDir
配置告诉lite-server将./src
和./build/contracts
目录作为web服务器的根目录,所有文件都可以被访问routes
把./node_modules
映射为/vendor
,在引用文件时,可以使用/vendor
创建前端页面
项目根目录下,创建src
目录,用于存放前端页面。
前端页面包含2个文件:
src/index.html
src/app.js
index.html
添加index.html
页面,内容如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>以太坊 DApp Demo</title> <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<h1>账号: <span id="account"></span></h1>
<hr>
<div id="content">
<h2>智能合约:MyContract</b></h2>
<p>获取智能合约中的value值: <span id="value"></span></p> <h5>设置value值</h5>
<form onSubmit="App.set(); return false;" role="form">
<div >
<input id="newValue" type="text"></input>
</div>
<button type="submit" >设置</button>
</form>
</div>
<div id="loader">正在加载...</div> </div> <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="https://etherscan.io/jss/web3.min.js"></script>
<script src="vendor/@truffle/contract/dist/truffle-contract.js"></script>
<script src="app.js"></script>
</body>
</html>
这个文件的重点是引入了几个js文件:
web3.min.js
– web3.js库文件,直接从https://etherscan.io/引入truffle-contract.js
– truffle提供的处理智能合约的库文件
安装@truffle/contract
$ npm install @truffle/contract --save-dev
app.js
添加javascript脚本文件:app.js
App = {
web3Provider: null,
contracts: {},
account: '0x0',
loading: false,
contractInstance: null, init: async () => {
// 加载web3
await App.loadWeb3()
// 加载智能合约
await App.loadContract()
// 网页刷新
await App.render()
}, // https://medium.com/metamask/https-medium-com-metamask-breaking-change-injecting-web3-7722797916a8
loadWeb3: async () => {
if (typeof web3 !== 'undefined') {
App.web3Provider = web3.currentProvider
web3 = new Web3(web3.currentProvider)
} else {
window.alert("Please connect to Metamask.")
}
// MetaMask新版本…
if (window.ethereum) {
window.web3 = new Web3(ethereum)
try {
// 向用户请求帐户访问
await ethereum.enable()
// 用户允许使用账户
web3.eth.sendTransaction({/* ... */ })
} catch (error) {
// 用户拒绝使用账户
}
}
// MetaMask老版本…
else if (window.web3) {
App.web3Provider = web3.currentProvider
window.web3 = new Web3(web3.currentProvider)
// 无需向用户请求,可以直接使用账号
web3.eth.sendTransaction({/* ... */ })
}
// 没有安装以太坊钱包插件(MetaMask)...
else {
console.log('需要安装以太坊钱包插件(例如MetaMask)才能使用!')
}
}, loadContract: async () => {
const contract = await $.getJSON('MyContract.json')
App.contracts.MyContract = TruffleContract(contract)
App.contracts.MyContract.setProvider(App.web3Provider)
}, render: async () => {
// 如果正在加载,直接返回,避免重复操作
if (App.loading) {
return
} // 更新app加载状态
App.setLoading(true) // 设置当前区块链帐户
const accounts = await ethereum.enable()
App.account = accounts[0]
$('#account').html(App.account) // 加载智能合约
const contract = await App.contracts.MyContract.deployed()
App.contractInstance = contract const value = await App.contractInstance.get()
$('#value').html(value) App.setLoading(false)
}, set: async () => {
App.setLoading(true) const newValue = $('#newValue').val() await App.contractInstance.set(newValue, {from: App.account})
window.alert('更新成功,页面值不会马上更新,等待几秒后多刷新几次。')
App.setLoading(false)
}, setLoading: (boolean) => {
App.loading = boolean
const loader = $('#loader')
const content = $('#content')
if (boolean) {
loader.show()
content.hide()
} else {
loader.hide()
content.show()
}
}
} $(document).ready(function () {
App.init()
});
在上面的代码中:
loadWeb3()
函数添加了用Metamask将web浏览器连接到区块链所需的配置。这是直接从Metamask的配置规范中复制粘贴的,如函数上面代码注释中的url所示。loadContract()
函数使用TruffleContract
库创建智能合约的javascript对象,可以用于调用智能合约中的函数。render()
函数设置页面内容,包括帐户及智能合约的value值。
现在启动服务器:
$ npm run dev
然后使用安装了MetaMask插件的Chrome浏览器,打开网址:http://localhost:3000,就可以查看前端页面,与区块链上的智能合约进行交互。
以太坊(Ethereum) – truffle脚本
Truffle包含一个脚本运行器,可对以太坊网络执行自定义脚本。
让我们创建一个脚本并执行。
在项目根目录下,创建script.js
文件,内容如下:
module.exports = function(callback) {
web3.eth.getBlock('latest').then(console.log)
}
该脚本将从Kovan测试网络获取最新区块的信息。
执行脚本:
truffle exec script.js --network kovan
输出:
{ author: '0x596e8221a30bfe6e7eff67fee664a01c73ba3c56',
difficulty: '340282366920938463463374607431768211454',
extraData:
'0xde830205058f5061726974792d457468657265756d86312e33362e30826c69',
gasLimit: '0x7a1200',
gasUsed: '0x5e61',
hash:
'0x225a1e0b13fd20396af60d049ce9bb94c2f3f7df06c7db260880b62c91997004',
logsBloom:
'0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
miner: '0x596e8221A30bFe6e7eFF67Fee664A01C73BA3C56',
number: 13448162,
parentHash:
'0x28d00fd7b66771130ed98de5073c7797ee293e7bee4b546793a4b79171555066',
receiptsRoot:
'0x44617b5733ee59bde159af08ffd6edae36e0964f1724c333f3d1bef0808dee15',
sealFields:
[ '0x84175e95d7',
'0xb8412ed900e67f4a72925fb3b495efb3f547411f40d26e972cc0e8b2cf26e40cf84a545e0328199d4880b79c62670129a7db12ac58234bee0866c6376b46ab99e8a200' ],
sha3Uncles:
'0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347',
signature:
'2ed900e67f4a72925fb3b495efb3f547411f40d26e972cc0e8b2cf26e40cf84a545e0328199d4880b79c62670129a7db12ac58234bee0866c6376b46ab99e8a200',
size: 797,
stateRoot:
'0xe1bbaacfb950361bec70f4ad53a2605e1ac1d2ff0bfd913fe063dc6c5f3252a0',
step: '392074711',
timestamp: 1568298844,
totalDifficulty: '4525729278306228651801195598997744985609807728',
transactions:
[ '0xf1ae41eac6b32419bc62a6cde9cab4b4ca244899a3d49b4a2461bcf94f504176' ],
transactionsRoot:
'0xf08c8097ea946f84ce9594ce73648fc0f9f683adef105a5db00c5f1f15e61c2c',
uncles: [] }
下面的代码智能合约MyContract
中,读取value值,将script.js
脚本文件中的代码替换为:
const MyContract = artifacts.require("./MyContract.sol");
module.exports = async function(callback) {
const contract = await MyContract.deployed()
const value = await contract.get()
console.log("Value:", value)
}
执行脚本:
truffle exec script.js --network kovan
输出:
Value: hello world
脚本运行器是一个非常有用的功能。
Doc navigation
类似Java中JUnit单元测试工具,Trfuffle test可以帮助我们对智能合约项目进行白盒测试。
对于区块链项目,测试显得尤其重要,因为部署合约、迁移合约的成本都是相当高的,都要消耗Gas。
编写测试代码
现在让我们对前面章节中创建的智能合约,编写一些测试代码。整个测试过程模拟对智能合约MyContract
获取value
值、设置value
值的过程。
先确保MyContract
已经正确部署到Ganache本地区块链网络中。测试中将会用到Mocha测试框架,与Chai断言库,但Truffle已经集成了这些库。
测试代码用JavaScript编写,模拟与智能合约的交互,就像使用truffle console所做的那样。
在项目根目录下的test
目录中,添加测试脚本文件: MyContract.js
MyContract.js
中的测试代码:
// 首先,`require`合约并将其分配给一个变量`MyContract`
const MyContract = artifacts.require('./MyContract.sol'); // 调用“contract”函数,并在回调函数中编写所有测试
// 回调函数提供一个“accounts”变量,表示本地区块链上的所有帐户。
contract('MyContract', (accounts) => { // 第1个测试:调用get()函数,检查返回值,测试合约中value初始值是否是: 'myValue'
it('initializes with the correct value', async () => {
// 获取合约实例
const myContract = await MyContract.deployed()
const value = await myContract.get()
// 使用断言测试value的值
assert.equal(value, 'myValue')
}) // 第2个测试: 调用set()函数来设置value值,然后调用get()函数来确保更新了值
it('can update the value', async () => {
const myContract = await MyContract.deployed()
myContract.set('New Value');
const value = await myContract.get()
assert.equal(value, 'New Value')
})
})
代码说明,请见注释。
运行测试脚本
执行命令行运行测试:
$ truffle test
测试详情:
G:\qikegu\ethereum\mydapp>truffle test
Using network 'development'. Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile. Contract: MyContract
√ initializes with the correct value (76ms)
√ can update the value (78ms) 2 passing (188ms)
以太坊(Ethereum) – 连接公链
本篇介绍如何将Truffle项目连接到公共区块链网络。
到目前为止,我们连接的都是在本地区块链(Ganache),接下来将连接以太坊公链。
以太坊公链除了主网,还有多个测试网络。主网(Mainnet)是正式的以太坊网络,里面的以太币是真正有价值的,测试网络中的以太币没有价值,只用于测试。
我们最终目标是连接到主网,但先连接到测试网络Kovan,虽然本地区块链网络(Ganache)也能测试,但与公链还是有区别的。
连接到公链的步骤如下:
- 设置钱包来管理公链帐户
- 连接到以太坊节点
- 更新项目设置
- 访问以太坊节点
设置钱包
首先需要设置一个钱包,来管理我们的公链帐户。
简单起见,可以借用Ganache本地区块链钱包,由于区块链的工作原理,这个钱包在公共区块链和本地区块链上都是有效的。
打开Ganache,主界面上可以看到一个名为“MNEMONIC”的部分:
这是一个种子短语,用于构建由Ganache管理的钱包。我们可以使用这个种子短语加密重建钱包,来连接到公链。
复制这个值,保存到一个秘密文件,MNEMONIC是一个秘密值,需要保密。在项目根目录中创建一个.env
文件,保存MNEMONIC值,如下所示:
MNEMONIC="你的mnemonic"
连接以太坊节点
现在已经创建了钱包,下一步需要访问Ethereum节点,以便连接到公共区块链网络。
有几种方法可以做到这一点,可以使用Geth或Parity运行自己的Ethereum节点。但这需要从区块链下载大量数据并保持同步,很麻烦。
比较方便的方法是,使用Infura访问Ethereum节点。Infura是一个免费提供Ethereum节点的服务。
在Infura上注册账号,创建项目,在项目详情页上可以查看API KEY:
使用API KEY,就可以访问以太坊网络节点。
在.env
文件中添加Infura api key的配置:
INFURA_API_KEY="https://kovan.infura.io/v3/543526cd4d3846acbc3826484e934564"
MNEMONIC="你的mnemonic"
更新项目设置
接下来使用MNEMONIC
与INFURA_API_KEY
,更新项目的网络配置,以便连接到公共区块链网络。
修改truffle-config.js
文件:
// 导入dotenv库创用于读取`.env`文件中的设置
require('dotenv').config();
// 导入truffle-hdwallet-provider库重建钱包
const HDWalletProvider = require('truffle-hdwallet-provider'); module.exports = {
networks: {
development: {
host: "127.0.0.1", // Localhost (default: none)
port: 7545, // Standard Ethereum port (default: none)
network_id: "*", // Any network (default: none)
},
// Useful for deploying to a public network.
// NB: It's important to wrap the provider as a function.
kovan: {
provider: () => new HDWalletProvider(
process.env.MNEMONIC,
process.env.INFURA_API_KEY
),
gas: 5000000,
gasPrice: 25000000000,
network_id: 42
},
},
solc: {
optimizer: {
enabled: true,
runs: 200
}
}
}
可以看到,我们使用了.env
配置文件中的MNEMONIC
与INFURA_API_KEY
配置了kovan网络。
由于用到了dotenv与truffle-hdwallet-provider这2个库,我们需要先安装:
切换到项目目录,执行以下命令
npm install dotenv --save-dev
npm install truffle-hdwallet-provider --save-dev
注意 安装truffle-hdwallet-provider时,如果出现node-gyp相关的错误,可参考这里解决。
访问以太坊节点
使用truffle console
连接到公共区块链网络:
$ truffle console --network kovan
要验证连接,可以从区块链中读取一些数据,获取一些关于最新区块的信息,在控制台上执行:
web3.eth.getBlock('latest').then(console.log)
输出
{ author: '0x03801efb0efe2a25ede5dd3a003ae880c0292e4d',
difficulty: '',
extraData:
'0xde830206028f5061726974792d457468657265756d86312e33362e30826c69',
gasLimit: '0x7a1200',
gasUsed: '0x17d23',
hash:
'0xc7390c4f492c8c1da60608135fc9e05930123b645b39f221cba33d8b3c577b2a',
logsBloom:
'0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000080000000000000000000100000008000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400800000000000010000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000008000000',
receiptsRoot:
'0x3d05bb2ed4fcc90234eea6d840e7d0e3ce7f598a15e5314536b17bcd11c78b5b',
sealFields:
[ '0x84175e8801',
'0xb84155a8cdb108dccec1d314124058fa6f22e7400ee200db0a94b7b165e4c3454c1818cc05f815cb7ce48f7a88b8401515740311a3566d9cf079428d506a6daca50101' ],
sha3Uncles:
'0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347',
signature:
'55a8cdb108dccec1d314124058fa6f22e7400ee200db0a94b7b165e4c3454c1818cc05f815cb7ce48f7a88b8401515740311a3566d9cf079428d506a6daca50101',
size: 877,
stateRoot:
'0x03af5adce52a81ce5d332cddb9955e344214bff00859b78868116e1e839efdf7',
step: '',
timestamp: 1568284676,
totalDifficulty: '',
transactions:
[ '0xded7fed0842fd65ec808bc3652ec4175bc190acc11345c49c44b1fb5d954610f',
'0x7e9112a46fa3c07aad813ea86355b15eebb44023c040d198ee7d15d379bbc2be' ],
transactionsRoot:
'0x0dd10d90686dda2684bd0ba70d1c9e1d9a5302c30ca75eb2c5b07a7b6e4498b9',
uncles: [] }
可以看到,已经成功连接到了公链。
以太坊(Ethereum) – 部署智能合约到公链
现在,我们将智能合约部署到公链。步骤如下:
- 部署需要消耗Gas,获取测试以太币用于部署
- 部署智能合约
- 验证部署
获取测试以太币
部署需要消耗Gas,Gas需要支付以太币,我们部署到的是公链测试网Kovan,网络中的以太币没有市场价值。
可以从Kovan faucet Gitter聊天室获取测试用的伪以太币。只需把钱包地址发送出去,约5分钟内,有人会给你发测试用的伪以太币。
打开Ganache并复制列表中第一个帐户的地址(钱包地址),类似下面所示:
0x29920e756f41F8e691aE0b12D417C19204371E91
发送到聊天室内,稍等片刻,你的账号将收到一笔以太币。
部署智能合约
现在帐户里已经有了资金,可以进行部署了。
执行部署命令:
truffle migrate --network kovan
一旦部署完成,应该会看到部署成功的消息。
部署命令执行详情:
G:\qikegu\ethereum\mydapp>truffle migrate --network kovan Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile. Migrations dry-run (simulation)
===============================
> Network name: 'kovan-fork'
> Network id: 42
> Block gas limit: 0x7a1200 ... Starting migrations...
======================
> Network name: 'kovan'
> Network id: 42
> Block gas limit: 0x7a1200 1_initial_migration.js
====================== Deploying 'Migrations'
----------------------
> transaction hash: 0x7e30b5c716afed45888a9dd2d6af7e6f52a9fade0346e8ad7d0c268de508a26a
> Blocks: 2 Seconds: 9
> contract address: 0x168A7247B58786edd259502948f5Bf9449C863AD
> block number: 13447029
> block timestamp: 1568294312
> account: 0x29920e756f41F8e691aE0b12D417C19204371E91
> balance: 2.993465175
> gas used: 261393
> gas price: 25 gwei
> value sent: 0 ETH
> total cost: 0.006534825 ETH > Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.006534825 ETH 2_deploy_contracts.js
===================== Deploying 'MyContract'
----------------------
> transaction hash: 0xc1f7ec8fee1a23e3d08d0c9e9d6e15fef24feb8ba163e0071dccb1bb90cc0eca
> Blocks: 0 Seconds: 0
> contract address: 0x4D3CFaF8457CEA76c0409f989f9870115B4d2d82
> block number: 13447036
> block timestamp: 1568294340
> account: 0x29920e756f41F8e691aE0b12D417C19204371E91
> balance: 2.9850534
> gas used: 294448
> gas price: 25 gwei
> value sent: 0 ETH
> total cost: 0.0073612 ETH > Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.0073612 ETH Summary
=======
> Total deployments: 2
> Final cost: 0.013896025 ETH Summary
=======
> Total deployments: 2
> Final cost: 0.013896025 ETH
验证部署
现在打开truffle控制台,与kovan测试网络上的智能合约进行交互:
$ truffle console --network kovan
在控制台中执行:
truffle(kovan)> MyContract.deployed().then((c) => { contract = c })
然后:
truffle(kovan)> contract.get()
'myValue'
truffle(kovan)> contract.set("hello world")
{ tx:
'0x7bf63444f3a7bd70e981a7bd49228b1cf1a8c3754daf64c4c7765b8eee46bf37',
receipt:
{ blockHash:
'0xe03d0f43d85f4e41c18a90aa563ebda08899c6b9c38d0cd7779937046e2aed0c',
blockNumber: 13447763,
contractAddress: null,
cumulativeGasUsed: 33629,
from: '0x29920e756f41f8e691ae0b12d417c19204371e91',
gasUsed: 33629,
logs: [],
logsBloom:
'0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
root: null,
status: true,
to: '0x4d3cfaf8457cea76c0409f989f9870115b4d2d82',
transactionHash:
'0x7bf63444f3a7bd70e981a7bd49228b1cf1a8c3754daf64c4c7765b8eee46bf37',
transactionIndex: 0,
rawLogs: [] },
logs: [] }
truffle(kovan)> contract.get()
'hello world'
可以看到智能合约已经成功部署。
以太坊(Ethereum) – truffle脚本
Truffle包含一个脚本运行器,可对以太坊网络执行自定义脚本。
让我们创建一个脚本并执行。
在项目根目录下,创建script.js
文件,内容如下:
module.exports = function(callback) {
web3.eth.getBlock('latest').then(console.log)
}
该脚本将从Kovan测试网络获取最新区块的信息。
执行脚本:
truffle exec script.js --network kovan
输出
{ author: '0x596e8221a30bfe6e7eff67fee664a01c73ba3c56',
difficulty: '',
extraData:
'0xde830205058f5061726974792d457468657265756d86312e33362e30826c69',
gasLimit: '0x7a1200',
gasUsed: '0x5e61',
hash:
'0x225a1e0b13fd20396af60d049ce9bb94c2f3f7df06c7db260880b62c91997004',
logsBloom:
'0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
miner: '0x596e8221A30bFe6e7eFF67Fee664A01C73BA3C56',
number: 13448162,
parentHash:
'0x28d00fd7b66771130ed98de5073c7797ee293e7bee4b546793a4b79171555066',
receiptsRoot:
'0x44617b5733ee59bde159af08ffd6edae36e0964f1724c333f3d1bef0808dee15',
sealFields:
[ '0x84175e95d7',
'0xb8412ed900e67f4a72925fb3b495efb3f547411f40d26e972cc0e8b2cf26e40cf84a545e0328199d4880b79c62670129a7db12ac58234bee0866c6376b46ab99e8a200' ],
sha3Uncles:
'0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347',
signature:
'2ed900e67f4a72925fb3b495efb3f547411f40d26e972cc0e8b2cf26e40cf84a545e0328199d4880b79c62670129a7db12ac58234bee0866c6376b46ab99e8a200',
size: 797,
stateRoot:
'0xe1bbaacfb950361bec70f4ad53a2605e1ac1d2ff0bfd913fe063dc6c5f3252a0',
step: '',
timestamp: 1568298844,
totalDifficulty: '',
transactions:
[ '0xf1ae41eac6b32419bc62a6cde9cab4b4ca244899a3d49b4a2461bcf94f504176' ],
transactionsRoot:
'0xf08c8097ea946f84ce9594ce73648fc0f9f683adef105a5db00c5f1f15e61c2c',
uncles: [] }
下面的代码智能合约MyContract
中,读取value值,将script.js
脚本文件中的代码替换为:
const MyContract = artifacts.require("./MyContract.sol"); module.exports = async function(callback) {
const contract = await MyContract.deployed()
const value = await contract.get()
console.log("Value:", value)
}
执行脚本:
truffle exec script.js --network kovan
输出
Value: hello world
以太坊(Ethereum) – 让浏览器支持区块链(MetaMask)
大多数web浏览器目前都不支持连接到区块链网络,不过可以通过安装浏览器插件,来让浏览器支持区块链。
安装MetaMask
我们将为Chrome浏览器安装Metamask钱包插件(需翻墙)。
安装好后,确保插件的启用按钮打开,在浏览器的右上角会看到一个狐狸图标。
导入账号
把钱包账号从Ganache导入到Metamask中,这样我们就可以连接到区块链了。
打开Ganache主界面,如下图所示,复制MNEMONIC的值:
打开Metamask,选择通过Seed Phrase导入账号,把复制MNEMONIC的值,粘贴到Wallet Seed,如下图所示:
进入钱包
查看Kovan网络,可以看到里面有一些测试以太币余额。
现在我们的Chrome浏览器已经支持区块链了。
以太坊(Ethereum) – 智能合约前端页面
现在我们来开发智能合约的前端页面,让用户可以通过前端页面与智能合约交互。这个页面的主要功能是:
- 显示当前连接的帐户
- 读取智能合约中存储的value值
- 更新智能合约中存储的value值
页面大概的样子:
为开发前端页面,需要完成下面几项工作:
- 配置web服务器,用来部署页面
- 创建前端的h5、js文件
配置web服务器
首先,让我们来配置web服务器。服务器使用lite-server,安装lite-server:
$ npm install lite-server --save-dev
项目根目录下,创建lite-server的配置文件bs-config.json
,内容如下:
{
"server": {
"baseDir": [
"./src",
"./build/contracts"
],
"routes": {
"/vendor": "./node_modules"
}
}
}
baseDir
配置告诉lite-server将./src
和./build/contracts
目录作为web服务器的根目录,所有文件都可以被访问routes
把./node_modules
映射为/vendor
,在引用文件时,可以使用/vendor
创建前端页面
项目根目录下,创建src
目录,用于存放前端页面。
前端页面包含2个文件:
src/index.html
src/app.js
index.html
添加index.html
页面,内容如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>以太坊 DApp Demo</title> <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<h1>账号: <span id="account"></span></h1>
<hr>
<div id="content">
<h2>智能合约:MyContract</b></h2>
<p>获取智能合约中的value值: <span id="value"></span></p> <h5>设置value值</h5>
<form onSubmit="App.set(); return false;" role="form">
<div >
<input id="newValue" type="text"></input>
</div>
<button type="submit" >设置</button>
</form>
</div>
<div id="loader">正在加载...</div> </div> <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="https://etherscan.io/jss/web3.min.js"></script>
<script src="vendor/@truffle/contract/dist/truffle-contract.js"></script>
<script src="app.js"></script>
</body>
</html>
这个文件的重点是引入了几个js文件:
web3.min.js
– web3.js库文件,直接从https://etherscan.io/引入truffle-contract.js
– truffle提供的处理智能合约的库文件
安装@truffle/contract
$ npm install @truffle/contract --save-dev
app.js
添加javascript脚本文件:app.js
App = {
web3Provider: null,
contracts: {},
account: '0x0',
loading: false,
contractInstance: null, init: async () => {
// 加载web3
await App.loadWeb3()
// 加载智能合约
await App.loadContract()
// 网页刷新
await App.render()
}, // https://medium.com/metamask/https-medium-com-metamask-breaking-change-injecting-web3-7722797916a8
loadWeb3: async () => {
if (typeof web3 !== 'undefined') {
App.web3Provider = web3.currentProvider
web3 = new Web3(web3.currentProvider)
} else {
window.alert("Please connect to Metamask.")
}
// MetaMask新版本…
if (window.ethereum) {
window.web3 = new Web3(ethereum)
try {
// 向用户请求帐户访问
await ethereum.enable()
// 用户允许使用账户
web3.eth.sendTransaction({/* ... */ })
} catch (error) {
// 用户拒绝使用账户
}
}
// MetaMask老版本…
else if (window.web3) {
App.web3Provider = web3.currentProvider
window.web3 = new Web3(web3.currentProvider)
// 无需向用户请求,可以直接使用账号
web3.eth.sendTransaction({/* ... */ })
}
// 没有安装以太坊钱包插件(MetaMask)...
else {
console.log('需要安装以太坊钱包插件(例如MetaMask)才能使用!')
}
}, loadContract: async () => {
const contract = await $.getJSON('MyContract.json')
App.contracts.MyContract = TruffleContract(contract)
App.contracts.MyContract.setProvider(App.web3Provider)
}, render: async () => {
// 如果正在加载,直接返回,避免重复操作
if (App.loading) {
return
} // 更新app加载状态
App.setLoading(true) // 设置当前区块链帐户
const accounts = await ethereum.enable()
App.account = accounts[0]
$('#account').html(App.account) // 加载智能合约
const contract = await App.contracts.MyContract.deployed()
App.contractInstance = contract const value = await App.contractInstance.get()
$('#value').html(value) App.setLoading(false)
}, set: async () => {
App.setLoading(true) const newValue = $('#newValue').val() await App.contractInstance.set(newValue, {from: App.account})
window.alert('更新成功,页面值不会马上更新,等待几秒后多刷新几次。')
App.setLoading(false)
}, setLoading: (boolean) => {
App.loading = boolean
const loader = $('#loader')
const content = $('#content')
if (boolean) {
loader.show()
content.hide()
} else {
loader.hide()
content.show()
}
}
} $(document).ready(function () {
App.init()
});
在上面的代码中:
loadWeb3()
函数添加了用Metamask将web浏览器连接到区块链所需的配置。这是直接从Metamask的配置规范中复制粘贴的,如函数上面代码注释中的url所示。loadContract()
函数使用TruffleContract
库创建智能合约的javascript对象,可以用于调用智能合约中的函数。render()
函数设置页面内容,包括帐户及智能合约的value值。
现在启动服务器:
$ npm run dev
然后使用安装了MetaMask插件的Chrome浏览器,打开网址:http://localhost:3000,就可以查看前端页面,与区块链上的智能合约进行交互。
以太坊(Ethereum) – truffle脚本
Truffle包含一个脚本运行器,可对以太坊网络执行自定义脚本。
让我们创建一个脚本并执行。
在项目根目录下,创建script.js
文件,内容如下:
module.exports = function(callback) {
web3.eth.getBlock('latest').then(console.log)
}
该脚本将从Kovan测试网络获取最新区块的信息。
执行脚本:
truffle exec script.js --network kovan
输出:
{ author: '0x596e8221a30bfe6e7eff67fee664a01c73ba3c56',
difficulty: '340282366920938463463374607431768211454',
extraData:
'0xde830205058f5061726974792d457468657265756d86312e33362e30826c69',
gasLimit: '0x7a1200',
gasUsed: '0x5e61',
hash:
'0x225a1e0b13fd20396af60d049ce9bb94c2f3f7df06c7db260880b62c91997004',
logsBloom:
'0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
miner: '0x596e8221A30bFe6e7eFF67Fee664A01C73BA3C56',
number: 13448162,
parentHash:
'0x28d00fd7b66771130ed98de5073c7797ee293e7bee4b546793a4b79171555066',
receiptsRoot:
'0x44617b5733ee59bde159af08ffd6edae36e0964f1724c333f3d1bef0808dee15',
sealFields:
[ '0x84175e95d7',
'0xb8412ed900e67f4a72925fb3b495efb3f547411f40d26e972cc0e8b2cf26e40cf84a545e0328199d4880b79c62670129a7db12ac58234bee0866c6376b46ab99e8a200' ],
sha3Uncles:
'0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347',
signature:
'2ed900e67f4a72925fb3b495efb3f547411f40d26e972cc0e8b2cf26e40cf84a545e0328199d4880b79c62670129a7db12ac58234bee0866c6376b46ab99e8a200',
size: 797,
stateRoot:
'0xe1bbaacfb950361bec70f4ad53a2605e1ac1d2ff0bfd913fe063dc6c5f3252a0',
step: '392074711',
timestamp: 1568298844,
totalDifficulty: '4525729278306228651801195598997744985609807728',
transactions:
[ '0xf1ae41eac6b32419bc62a6cde9cab4b4ca244899a3d49b4a2461bcf94f504176' ],
transactionsRoot:
'0xf08c8097ea946f84ce9594ce73648fc0f9f683adef105a5db00c5f1f15e61c2c',
uncles: [] }
下面的代码智能合约MyContract
中,读取value值,将script.js
脚本文件中的代码替换为:
const MyContract = artifacts.require("./MyContract.sol");
module.exports = async function(callback) {
const contract = await MyContract.deployed()
const value = await contract.get()
console.log("Value:", value)
}
执行脚本:
truffle exec script.js --network kovan
输出:
Value: hello world
脚本运行器是一个非常有用的功能。
Doc navigation
区块链入门到实战(27)之以太坊(Ethereum) – 智能合约开发的更多相关文章
- 区块链入门到实战(17)之以太坊(Ethereum) – 是什么
以太坊的作用:构建基于区块链的分布式应用. 以太坊是什么:可编程的虚拟币. 以太坊(Ethereum)是一个可编程的虚拟币,它是一个基于公共区块链的分布式计算平台,可用于构建基于区块链的分布式应用. ...
- 区块链入门到实战(38)之Solidity – 条件语句
Solidity支持条件语句,让程序可以根据条件执行不同的操作.条件语句包括: if if...else if...else if 语法 if (条件表达式) { 被执行语句(如果条件为真) } 示例 ...
- 区块链入门到实战(37)之Solidity – 循环语句
与其他语言类似,Solidity语言支持循环结构,Solidity提供以下循环语句. while do ... while for 循环控制语句:break.continue. Solidity – ...
- 区块链入门到实战(36)之Solidity – 运算符
Solidity – 算术运算符 Solidity 支持的算术运算符,如下表所示: 假设变量A的值为10,变量B的值为20. 序号 运算符与描述 1 + (加)求和例: A + B = 30 2 – ...
- 程序员的自我救赎---12.2.3: 虚拟币交易平台(区块链) 下 【C#与以太坊通讯】
<前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...
- 零门槛,包教会。让你在5分钟内使用以太坊ERC20智能合约发行属于自己的空气币
前言 目前区块链是互联网中最最火的风口,没有之一.我周围的很多朋友也加入了“炒币”行列,但很不幸,几乎都被“割韭菜”了.而经过我的几天研究,发现,如果自己要发行一种空气币,简直太简单了.只需要下面几个 ...
- 以太坊solidity智能合约-生成随机数
Solidity随机数生成 在以太坊的只能合约中,没有提供像其他面向对象编程一样的生成随机数的工具类或方法.其实,所谓的随机数也是伪随机的,没有哪一种语言能够真正的生成随机数. 对于solidity来 ...
- 区块链入门到实战(26)之以太坊(Ethereum) – 挖矿
以太坊(Ethereum)与其他公共区块链一样,使用工作量证明机制确保区块链网络正常运行. 矿工进行工作量证明计算,即挖矿,来选择区块,写入区块链,确认交易. 交易过程如下图所示: 从技术角度来看,以 ...
- 区块链入门到实战(24)之以太坊(Ethereum) – 网络节点
用途: 全节点:用于区块和交易的校验 轻节点:电子钱包 以太坊(Ethereum)网络是一个公共的区块链网络,网络中包含2种网络节点: 全节点 轻节点 全节点 包含了从初始区块开始的全部区块,这些区块 ...
随机推荐
- 使用types库修改函数
import types class ppp: pass p = ppp()#p为ppp类实例对象 def run(self): print("run函数") r = types. ...
- luogu P4884 多少个1?
LINK:多少个1? 题目要求:\(\sum_{i=0}^{n-1}10^i \equiv k \mod m\) 最小的n. 看起来很难求的样子 这个同余式 看起来只能暴力枚举. 不过既然是同余 我们 ...
- Pintech品致-高压放大器
pintech品致推出的HA-520(200KHz,500Vp-p)高压放大器真的是实用的高电压信号放大器, 体积小,轻便及简易的操作, 高电压输出(800Vp-p)等优点.连续输出电流量最大值达 ...
- MySQL一主多从配置和读写分离配置
一.一主多从配置 此次操作实现的是一主两从的方式.主服务器slave2(2.100),从服务器slave2-1(2.107),slave2-2(2.108);第一:准备主数据库 1. 在不同的机 ...
- vmware15虚拟机安装教程
自己碰到的问题:本人win7 64位旗舰版系统.之前用VMware12pro版本的软件,在安装Ubuntu18.04之后,有时候开启Ubuntu虚拟机时有点问题,重启就可以了.但是不稳定,所以改用VM ...
- Qt使用MD5加密
Qt中包含了大部分常用的功能,比如json.数据库.网络通信.串口通信以及今天说的这个MD5加密: Qt中将字符串进行MD5加密其实比较简单,代码如下: #include <QCoreAppli ...
- HTTP POST 请求的两种编码格式:application/x-www-form-urlencoded 和 multipart/form-data
在常见业务开发中,POST 请求常常在这些地方使用:前端表单提交时.调用接口代码时和使用 Postman 测试接口时.我们下面来一一了解: 一.前端表单提交时 application/x-www-fo ...
- C语言学习笔记之数据类型转换
1.整数与整数相除,结果也为整数 2.不同类型的运算,精度低的向精度高的转化 整数与浮点数运算就是个很好的例子,只要有一方为浮点数,结果也是浮点数,这也体现出精度低向精度高转化 3.在赋值运算中,等号 ...
- spring data jpa 之 通用接口
园主这一阵子接到一个需求,就是将spring data jpa再进行封装,实现通过调用一个baseRepository,来实现每个类的增删改查操作,结合spring data jpa 原有的便捷操作, ...
- 基于索引的QA问答对匹配流程梳理
知识库(主要是标准的QA信息)匹配需求是对已经梳理出的大量标准QA对信息进行匹配,找出最符合用户问题的QA对进行回复,拆分主要的处理流程主要为如下两点: 标准QA信息入库索引: 通过对用户提出的问题进 ...