当人工智能遇到区块链(二)

ce4ec0ce1e2bcc23f9bf62bc3ef22740.gif

前言

上回说到,在线Remix不方便调试,这一篇我们就搭建一下本地化环境部署,沿用上一篇里的智能合约和使用场景,从TruffleGanache开始配置开发环境。

Truffle

tuffle是一个基于以太坊虚拟机(EVM)的本地化智能合约开发环境,测试框架和上链工具。

https://trufflesuite.com/truffle/
adf2365ab012ed569f6efd42bc6c4b24.png

为了同时适用于linuxWindowsmac操作系统,这里我们统一采用命令行模式来搭建。

先安装nodejs环境,在官网下载安装包(或在文末获取这一篇提及的所有离线安装包),选择稳定版 16.12.2 LTS

# https://nodejs.org/en/
node -v  # 确认node安装完毕

全局安装 Truffle

npm install truffle -g
8e1b80b7c09bbc0060dd37b4f66a560d.png

初始化 yarn

yarn 代替 npm 来管理项目会更方便些

npm install yarn -g
457bd1b98aff069f7e17f3bcae0667de.png

生成项目

yarn init
f2ab5ea5193b6ab5da817246892d7e4a.png

初始化 truffle

创建一个目录,比如 main_contract

truffle init
f87302421ef6a03ab753bd643444d184.png

安装 openzeppelin

yarn add @openzeppelin/contracts
1d73e0124db4e0d96d465c540787ef70.png

VSCode

先将上一篇中编写的智能合约复制到contracts目录下,用 vscode 打开,就能看到一个典型智能合约项目的目录结构。

其中contracts目录里放置的是solidity智能合约,migrations目录里是部署文件,node_modules目录里是依赖的库文件,test目录下是测试文件。

266b59406d12a7c423ca9370dd7f4486.png

Ganache

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
15eed7599cd482b12a57a44122c9631b.png

本地化区块链

这里使用allowUnlimitedContractSize忽略所需部署合约的大小,并保持ganache在后台运行。

ganache-cli -d --allowUnlimitedContractSize
35c011089c063ed3749e606ec1f72e5d.png

生成了 10 个测试钱包,并将服务运行在 8545 端口,这里可以将钱包地址,私钥和助记词都备份一下。

绑定到 Metamask 钱包

先添加Ganache网络,填入RPC URLhttp://127.0.0.1:8545链ID1337

34955610843856fb0da31d2b9ad86ad6.png

然后通过私钥导入之前生成的10个钱包(前文备份过的)

673ee7e9a8731b34a6ae5c2987503ecc.png

一切顺利的话,就能看到每个账号里的 100 ETH 了。(记得网络要切换到Ganache上)

13b90949d179a16bdad1a4d7a1035cfe.png

部署合约

配置文件

修改配置文件 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
2afcae3b58425e3d3265456135afad45.png

部署主合约

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
9789be310ecb99b7153fe80f9cd76266.png

先使用编号为0的钱包,将主合约MainToken部署到网络上,完成后再部署子合约SubToken

相应的,可以在Ganache上看到交互信息。

bcc5adcb6f560d1379f7ad155ea55802.png

记录下主合约地址contract address0x9561c133dd8580860b6b7e504bc5aa500f0f06a7

部署子合约

之前部署主合约的时候,可以顺便将子合约一起部署完成(可以节省gas费用),但大多数情况,随着业务发展,子合约会晚于主合约部署,这时候就需要单独部署子合约。

创建3_initial_subtoken.js,参数填入主合约地址

const SubToken = artifacts.require("SubToken");

module.exports = function (deployer) {
  deployer.deploy(SubToken, "0x9561c133dd8580860b6b7e504bc5aa500f0f06a7");
};

指定部署3号合约(子合约)

truffle migrate --f 3
dd121e85dc709d3db654c17bb46f2c18.png

测试合约

引入合约

d09aa3c4f6d9a6e99646842a74d0c34a.png

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万枚给主钱包(合约拥有者钱包);

  • 主钱包拨一笔初始资金给子合约;

  • 子合约以主合约代币开展具体业务;

  • 子合约完成业务后,归还剩余代币给主合约;

  • 主合约提现到主钱包。

6ecaf416b4f892840db2dee5d12e011b.png
主钱包转账给子合约

从主钱包转账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
6f5cef855928b32f1ee4e2f89dbbecb3.png

这里只展示了部分正常功能的测试用例,实际项目中还需要补充更多的错误用例,做到全路径覆盖,来充分验证各种意外情况。好在,一个用例可以反复多次使用,后续简单调用truffle test即可完成测试,这在升级更新新合约的时候会特别有用。

geth

我们还可以用geth来搭建一个完整的以太坊节点,由于要同步历史上的账本,下载的数据量比较庞大,有兴趣可以根据以下链接来进行配置。

https://github.com/ethereum/go-ethereum

infura

为了部署到主链,除了用geth自行搭建,更简单的方法还可以使用infura

InfuraAPI 套件提供对以太坊和 IPFS 网络的即时 HTTPSWebSocket 访问。通过使用 Infura,可以轻松连接到 Web 3.0,而无需启动和维护您自己的基础架构。他们的核心服务是免费的,可以创建3个项目,每天最多10万个请求。

https://infura.io/

注册账号

需要先在官网上注册个账号,创建一个新项目,选择以太链,起一个项目名称

a660204d9ef20b33c19c0b3fbeec41a8.png

使用您的PROJECT IDROJECT SECRET进行身份验证,安全地复制您的密钥并选择适当的 ENDPOINTS

63d70bc5d64f0b7a881f96f18514352f.png

安装 HDWalletProvider

出于安全原因,Infura 不管理您的私钥,这意味着 Infura 无法代表您签署交易。但是,Truffle 可以通过使用HDWalletProvider处理交易签名以及与以太坊网络的连接。

1a2c20b2caf7e4566956937cc0249e41.png
yarn add @truffle/hdwallet-provider

Windows下如果安装错误,可以尝试安装构建工具,npm install -g windows-build-tools

配置 Truffle 项目

编辑 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/<INFURA_PROJECT_ID>")
      },
      network_id: 3
    }
  }
};

部署合约

选择测试网,默认是钱包的第一个账户,保证里面有测试币即可。

truffle migrate --network ropsten

Ganache 7.0 以上的话,还可以直接用fork.url来连接infura

ganache --fork.url wss://ropsten.infura.io/ws/v3/<INFURA_PROJECT_ID>
a4a64076a34aed179b444cd3d0f8405f.png

对比一下 Ganache CLI v6.12.2ganache v7.0.1 速度差异,虽然没有达到30倍,但的确快了很多。

源码下载

614e3729f6020b83c752da1114ec99aa.png

本期相关文件资料,可在公众号“深度觉醒”,后台回复:“chain02”,获取下载链接。

下一篇预告

这一篇主要介绍了一下本地开发区块链应用,后端用到的配套工具,以及测试方法。接下来,我们将配置react来编写前端,并用web3连接metamask来访问所部署的智能合约。

3bf84914ce8a82e41ae4d95a72f00a82.gif

猜你喜欢

转载自blog.csdn.net/weixin_47479625/article/details/122757779