上回说到,在线Remix
不方便调试,这一篇我们就搭建一下本地化环境部署,沿用上一篇里的智能合约和使用场景,从Truffle
和Ganache
开始配置开发环境。
tuffle
是一个基于以太坊虚拟机(EVM)的本地化智能合约开发环境,测试框架和上链工具。
https://trufflesuite.com/truffle/
为了同时适用于linux
,Windows
和mac
操作系统,这里我们统一采用命令行模式来搭建。
先安装nodejs
环境,在官网下载安装包(或在文末获取这一篇提及的所有离线安装包),选择稳定版 16.12.2 LTS
# https://nodejs.org/en/
node -v # 确认node安装完毕
Truffle
npm install truffle -g
用 yarn
代替 npm
来管理项目会更方便些
npm install yarn -g
生成项目
yarn init
创建一个目录,比如 main_contract
,
truffle init
yarn add @openzeppelin/contracts
先将上一篇中编写的智能合约复制到contracts
目录下,用 vscode
打开,就能看到一个典型智能合约项目的目录结构。
其中contracts
目录里放置的是solidity
智能合约,migrations
目录里是部署文件,node_modules
目录里是依赖的库文件,test
目录下是测试文件。
Truffle
的主要功能是将solidity
源码编译成能在EVM
中运行的执行文件,而Ganache
则模拟测试链,提供了一个本地化的区块链环境,用于部署合约。
Ganache
这里可以选择 6.12 或是最新的 7.0 版本,新版本加入了些新特性,具体差异可以看这里。
https://trufflesuite.com/blog/introducing-ganache-7/
npm install ganache-cli -g # 6.12
npm install ganache -g # 7.0
这里使用allowUnlimitedContractSize
忽略所需部署合约的大小,并保持ganache
在后台运行。
ganache-cli -d --allowUnlimitedContractSize
生成了 10 个测试钱包,并将服务运行在 8545 端口,这里可以将钱包地址,私钥和助记词都备份一下。
先添加Ganache
网络,填入RPC URL
为http://127.0.0.1:8545
,链ID
为1337
然后通过私钥导入之前生成的10个钱包(前文备份过的)
一切顺利的话,就能看到每个账号里的 100 ETH 了。(记得网络要切换到Ganache
上)
修改配置文件 truffle-config.js
, 网络配置,确认ip
和端口号与之前的ganache
的一致。
development: {
host: "127.0.0.1", // Localhost (default: none)
port: 8545, // Standard Ethereum port (default: none)
network_id: "*", // Any network (default: none)
},
编译配置,添加src
目录,开启优化器,添加contracts_build_directory
// Configure your compilers
contracts_build_directory: "./src/contracts/",
compilers: {
solc: {
version: "0.8.11", // Fetch exact version from solc-bin (default: truffle's version)
// docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
settings: { // See the solidity docs for advice about optimization and evmVersion
optimizer: {
enabled: true,
runs: 200
},
// evmVersion: "byzantium"
}
}
},
truffle
会自动编译contracts
目录下的合约,并根据配置输出到src/contracts/
目录下
truffle compile
在migrations
目录下,参考 1_initial_migration.js
,创建2_initial_maintoken.js
。
传入3个初始化参数,固定总发行量为2100万枚
const MainToken = artifacts.require("MainToken");
const SubToken = artifacts.require("SubToken");
module.exports = function (deployer) {
deployer.deploy(MainToken, "Bluishfish", "BLF", 21000000).then(() =>{
deployer.deploy(SubToken, MainToken.address)
});
};
从头开始运行所有迁移,运行迁移文件以部署合约。
truffle migrate --reset
先使用编号为0的钱包,将主合约MainToken
部署到网络上,完成后再部署子合约SubToken
。
相应的,可以在Ganache
上看到交互信息。
记录下主合约地址contract address
为0x9561c133dd8580860b6b7e504bc5aa500f0f06a7
。
之前部署主合约的时候,可以顺便将子合约一起部署完成(可以节省gas费用),但大多数情况,随着业务发展,子合约会晚于主合约部署,这时候就需要单独部署子合约。
创建3_initial_subtoken.js
,参数填入主合约地址
const SubToken = artifacts.require("SubToken");
module.exports = function (deployer) {
deployer.deploy(SubToken, "0x9561c133dd8580860b6b7e504bc5aa500f0f06a7");
};
指定部署3号合约(子合约)
truffle migrate --f 3
在test
目录下新建Token.test.js
文件
const MainToken = artifacts.require("./MainToken");
const SubToken = artifacts.require("./SubToken");
默认参数会传入Ganache
创建的10个测试账号,一般第一个账号作为合约部署者,如果需要多账号交互的话,可以命名为 owner1,owner2...。
contract("TestToken", ([deployer, owner1, owner2]) => {
console.log('deployer: ', deployer);
console.log('owner1: ', owner1);
console.log('owner2: ', owner2);
...
}
测试环境完全与部署环境隔离,所以每个测试用例都需要重新部署合约,用beforeEach
可以很方便的完成这个任务。
beforeEach(async () => {
main_instance = await MainToken.new("Bluishfish","BLF", 21000000);
sub_instance = await SubToken.new(main_instance.address);
})
用it
来编写测试用例,测试主合约别名和代币总量
it("测试主合约初始化", async()=>{
// console.log('主合约地址: ', main_instance.address)
const name = await main_instance.name();
assert.equal(name, "Bluishfish");
const symbol = await main_instance.symbol();
assert.equal(symbol, "BLF");
const totalSupply = await main_instance.totalSupply();
assert.equal(totalSupply, web3.utils.toWei('21000000', 'ether'));
})
这里我们将子合约里的_mainAddress
先改为public
,便于查看合约归属
it("测试子合约初始化", async()=>{
// console.log('子合约地址: ', sub_instance.address)
const main_address = await sub_instance._mainAddress();
assert.equal(main_instance.address, main_address);
})
转账功能沿用上次的流程图:
先在主合约发现固定总量代币,预挖2100万枚给主钱包(合约拥有者钱包);
主钱包拨一笔初始资金给子合约;
子合约以主合约代币开展具体业务;
子合约完成业务后,归还剩余代币给主合约;
主合约提现到主钱包。
从主钱包转账10000枚代币,到子合约。这里采用toWei
来计算精度,避免代码里有太多的零。
beforeEach(async () => {
// 主钱包转账给子合约
const result = await main_instance.transfer(
sub_instance.address,
web3.utils.toWei('10000', 'ether'),
{ from: deployer });
assert.equal(result.receipt.status, true);
})
it("测试主钱包转账给子合约", async()=>{
// 查询子合约是否到账 10000 代币
const balance = await main_instance.balanceOf(sub_instance.address);
assert.equal(balance.toString(), web3.utils.toWei('10000', 'ether'));
});
从子合约里转账 5000 枚代币,归还到主合约里,再查询账户余额情况。
it("测试子合约归还代币给主合约", async()=>{
// 子合约转 5000 代币到主合约
const result = await sub_instance.transferWithToken(
web3.utils.toWei('5000', 'ether'), { from: deployer });
assert.equal(result.receipt.status, true);
// 查询子合约余额
const balance = await main_instance.balanceOf(sub_instance.address);
assert.equal(balance.toString(), web3.utils.toWei('5000', 'ether'));
});
先从子合约转账 10000 枚代币到主合约,然后从主合约上提现到主钱包,并查询各个账号里的余额是否正确。
it("测试主合约提现到主钱包", async()=>{
// 转子合约 10000 代币到主合约
const result = await sub_instance.transferWithToken(
web3.utils.toWei('10000', 'ether'), { from: deployer });
assert.equal(result.receipt.status, true);
// 查询子合约余额是否为 0
var balance = await main_instance.balanceOf(sub_instance.address);
assert.equal(balance.toString(), '0');
// 提现到主钱包
await main_instance.withdraw({ from: deployer });
// 查询主合约余额是否为 0
balance = await main_instance.balanceOf(main_instance.address);
assert.equal(balance.toString(), '0');
// 查询主钱包余额是否为 2100万
balance = await main_instance.balanceOf(deployer);
assert.equal(balance.toString(), web3.utils.toWei('21000000', 'ether'));
});
一切就绪以后,在命令行中输入
truffle test
这里只展示了部分正常功能的测试用例,实际项目中还需要补充更多的错误用例,做到全路径覆盖,来充分验证各种意外情况。好在,一个用例可以反复多次使用,后续简单调用truffle test
即可完成测试,这在升级更新新合约的时候会特别有用。
我们还可以用geth
来搭建一个完整的以太坊节点,由于要同步历史上的账本,下载的数据量比较庞大,有兴趣可以根据以下链接来进行配置。
https://github.com/ethereum/go-ethereum
为了部署到主链,除了用geth
自行搭建,更简单的方法还可以使用infura
。
Infura
的 API
套件提供对以太坊和 IPFS
网络的即时 HTTPS
和 WebSocket
访问。通过使用 Infura
,可以轻松连接到 Web 3.0
,而无需启动和维护您自己的基础架构。他们的核心服务是免费的,可以创建3个项目,每天最多10万个请求。
https://infura.io/
需要先在官网上注册个账号,创建一个新项目,选择以太链,起一个项目名称
使用您的PROJECT ID
和 ROJECT SECRET
进行身份验证,安全地复制您的密钥并选择适当的 ENDPOINTS
。
出于安全原因,Infura
不管理您的私钥,这意味着 Infura
无法代表您签署交易。但是,Truffle
可以通过使用HDWalletProvider
处理交易签名以及与以太坊网络的连接。
yarn add @truffle/hdwallet-provider
在Windows
下如果安装错误,可以尝试安装构建工具,npm install -g windows-build-tools
。
编辑 truffle-config.js
,引入库文件
const HDWalletProvider = require("@truffle/hdwallet-provider");
设置助记词,记得用小额度的开发者钱包,千万不要泄露了!推荐存放在文件中,并设置gitignore
来排除密钥文件。
// const mnemonic = "在metamask/设置/安全与隐私/显示账户助记词";
const fs = require('fs');
const mnemonic = fs.readFileSync(".secret").toString().trim();
添加一个 Ropsten
网络定义,填入INFURA_PROJECT_ID
module.exports = {
networks: {
ropsten: {
provider: function() {
return new HDWalletProvider(mnemonic, "https://ropsten.infura.io/v3/")
},
network_id: 3
}
}
};
选择测试网,默认是钱包的第一个账户,保证里面有测试币即可。
truffle migrate --network ropsten
Ganache 7.0
以上的话,还可以直接用fork.url
来连接infura
。
ganache --fork.url wss://ropsten.infura.io/ws/v3/
对比一下 Ganache CLI v6.12.2
和 ganache v7.0.1
速度差异,虽然没有达到30倍,但的确快了很多。
本期相关文件资料,可在公众号“深度觉醒”,后台回复:“chain02”,获取下载链接。
这一篇主要介绍了一下本地开发区块链应用,后端用到的配套工具,以及测试方法。接下来,我们将配置react
来编写前端,并用web3
连接metamask
来访问所部署的智能合约。