Waffle使用初体验

一、什么是Waffle

Waffle是什么呢?我们直接看其文档上的介绍:

Waffle is a library for writing and testing smart contracts. Sweeter, simpler and faster than Truffle. Works with ethers-js.

大致意思为,它是一个编写和测试(以太坊上)智能合约的库,比Truffle更加好用,简单和快速,它使用了ethers-js

注:(ethers-js 是一个非常棒的以太坊js框架,个人觉得甚至好于web3.js)。

介绍完之后我们根据其官方文档进行初次使用。

二、安装

2.1、安装yarn

yarn安装链接为:https://yarn.bootcss.com/docs/install/#mac-stable,注意选择相应的操作系统。

2.2、新建一个yarn工程

打开一个终端,运行下面的命令:

mkdir waffle_test
cd waffle_test/
yarn init

然后一路回车,会出现如下提示:

success Saved package.json
✨  Done in 25.49s.

这说明我们的yarn工程已经建立好了。我们接着运行code .来使用VS Code打开该工程(如果没有code命令就手动打开VS Code)。

在VS Code里我们可以看到本工程仅有一个package.json,接下来我们一步一步充实该工程。

2.3、安装waffle及一些依赖

下面我们按照其官方文档介绍的流程进行操作:

2.3.1、安装ethereum-waffle

打开VS Code的终端菜单,新建一个终端,在其中运行:

yarn add --dev ethereum-waffle

2.3.2、安装智能合约依赖

yarn add @openzeppelin/contracts -D

可以看到,我们安装了openzeppelin提供的一些标准模板合约。

三、编写智能合约并编译

3.1、编写一个测试合约

在项目根目录下新建src目录,并在src目录下建立BasicToken.sol,内容如下:

pragma solidity ^0.6.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// Example class - a mock class using delivering from ERC20
contract BasicToken is ERC20 {
    
    
  constructor(uint256 initialBalance) ERC20("Basic", "BSC") public {
    
    
      _mint(msg.sender, initialBalance);
  }
}

可以看到,这是一个简单的测试用的ERC20合约,在构造器里初始化了发行总量。

3.2、编译合约

3.2.1、编辑package.json,添加如下内容。

{
    
    
  "scripts": {
    
    
    "build": "waffle"
  }
}

注意:官方文档提到,当waffle版本不同时,它的内容也不同。waffle 3.0.0以上(当前版本是3.2.0)就是这样设置;如果是2.5.0版本,则为:

{
    
    
  "scripts": {
    
    
    "build": "waffle waffle.json"
  }
}

我们当前是3.0.0以上版本,所以不需要手动在后面加waffle.json,默认值就是它。

3.2.2、创建waffle.json

在项目根目录下创建waffle.json,内容为:

{
    
    
  "compilerType": "solcjs",
  "compilerVersion": "0.6.2",
  "sourceDirectory": "./src",
  "outputDirectory": "./build"
}

它定义了一些最基本的配置,很容易看明白。但是这里按照官方文档实际操作有些问题,我们接下来会讲。

3.2.3、编译

运行yarn build

会提示获取不到0.6.2版本的编译器,怎么办呢?我们可以参考Uniswap项目,修改上面的waffle.json,将compilerVersion的值改为:"./node_modules/solc",官方文档有介绍compilerVersion的几种格式。

所以修改后的waffle.json为:

{
    
    
    "compilerType": "solcjs",
    "compilerVersion": "./node_modules/solc",
    "sourceDirectory": "./src",
    "outputDirectory": "./build"
}

再次运行yarn build,则会有Done in 5.93s.类似的提示,代表我们合约已经编译好了。这时可以看到,项目根目录下多了build目录,里面有多个json文件,这就是编译后合约对应的json文件。

四、编写测试

4.1、安装测试库

Waffle使用MochaChai进行测试,所以必须先安装它们:

yarn add --dev mocha chai

4.2、编写测试文件

安装完成之后,在项目根目录下新建test目录,然后在该目录下新建BasicToken.test.ts,内容如下:

import {
    
    expect, use} from 'chai';
import {
    
    Contract} from 'ethers';
import {
    
    deployContract, MockProvider, solidity} from 'ethereum-waffle';
import BasicToken from '../build/BasicToken.json';

use(solidity);

describe('BasicToken', () => {
    
    
  const [wallet, walletTo] = new MockProvider().getWallets();
  let token: Contract;

  beforeEach(async () => {
    
    
    token = await deployContract(wallet, BasicToken, [1000]);
  });

  it('Assigns initial balance', async () => {
    
    
    expect(await token.balanceOf(wallet.address)).to.equal(1000);
  });

  it('Transfer adds amount to destination account', async () => {
    
    
    await token.transfer(walletTo.address, 7);
    expect(await token.balanceOf(walletTo.address)).to.equal(7);
  });

  it('Transfer emits event', async () => {
    
    
    await expect(token.transfer(walletTo.address, 7))
      .to.emit(token, 'Transfer')
      .withArgs(wallet.address, walletTo.address, 7);
  });

  it('Can not transfer above the amount', async () => {
    
    
    await expect(token.transfer(walletTo.address, 1007)).to.be.reverted;
  });

  it('Can not transfer from empty account', async () => {
    
    
    const tokenFromOtherWallet = token.connect(walletTo);
    await expect(tokenFromOtherWallet.transfer(wallet.address, 1))
      .to.be.reverted;
  });

  it('Calls totalSupply on BasicToken contract', async () => {
    
    
    await token.totalSupply();
    expect('totalSupply').to.be.calledOnContract(token);
  });

  it('Calls balanceOf with sender address on BasicToken contract', async () => {
    
    
    await token.balanceOf(wallet.address);
    expect('balanceOf').to.be.calledOnContractWith(token, [wallet.address]);
  });
});

本测试文件由官方文档提供,具体测试内容很简单,有兴趣的读者可以自己看一下。这里因为主要是介绍waffle的使用,所以略过测试内容及测试文件的编写。

4.3、运行测试

根据其官方文档,运行测试前我们需要更新package.json,使之包括如下内容:

{
    
    
  "scripts": {
    
    
    "build": "waffle",
    "test": "export NODE_ENV=test && mocha",
  }
}

这里其实就是加上test脚本的运行命令。

然后运行yarn test

然而,会报出Error: No test files found: "test",这是为什么呢?严格按照官方文档操作怎么就报错了呢,笔者在这里也坑了许久。多方查阅及和waffle本身的示例对照之后发现,是少了一个Mocha的配置文件。因为我们安装的是最新的Mocha,所以这里需要修改一下package.json,添加一个mocha属性并设置,使之包含如下内容:

{
    
    
  "mocha":{
    
    
    "extension": ["ts"],
    "spec": "./test/**/*.test.ts",
    "require": "ts-node/register",
    "timeout": 12000
  },
}

然后再次运行yarn test,会报错说找不到一些依赖包,这个我们下一步来解决。

注意:这里需要说明的是,mocha配置文件的设置方法有好几种(查看mocha对应的官方文档)。Waffle在github仓库中的示例使用的方法不是在package里设置,而是在test目录下新建了一个mocha.opts,其内容为:

-r ts-node/register/transpile-only
--timeout 50000
--no-warnings
test/**/*.test.{
    
    js,ts}

然而经过测试,当前mocha的版本为8.2.1(这个可以从package.json中查看)时,它不会读取mocha.opts,所以就算test目录下有此文件,也仍然会出现Error: No test files found: "test"的问题。

解决该问题的办法有两种:

  1. mocha的版本回退,比如设置为"^7.1.2",然后再运行yarn来安装该版本的mocha,这样就可以读取配置文件了。
  2. 正如前面提到的,在package.json里(或者别处,参考mocha文档)进行设置而不是使用test/mocha.opts

4.4、安装其它依赖

接着刚才的操作,请确认是在package.json里进行mocha设置而不是使用mocha.opts

再次运行yarn test会提示ERROR: Error: Cannot find module 'ts-node/register'。这是一些依赖库未安装,我们来依次安装:

yarn add ts-node -D
yarn add typescript -D
yarn add @types/chai -D
yarn add @types/mocha -D

4.5、tsconfig.json

安装完成之后我们再次运行yarn test

然而,又又又报错了,这次提示为:error TS2732: Cannot find module '../build/BasicToken.json',意思为找不到目录下相应的json文件。这里通过比对其示例,发现少了一个tsconfig.json配置文件。

我们把它加上去,在项目根目录下新建tsconfig.json,内容如下(该内容直接复制的官方示例):

{
    
    
  "compilerOptions": {
    
    
    "declaration": true,
    "esModuleInterop": true,
    "lib": [
      "ES2018"
    ],
    "module": "CommonJS",
    "moduleResolution": "node",
    "outDir": "dist",
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "strict": true,
    "target": "ES2018"
  }
}

再次运行yarn test,会有类似如下输出:

yarn run v1.22.5
$ export NODE_ENV=test && mocha


  BasicToken
    ✓ Assigns initial balance
    ✓ Transfer adds amount to destination account (120ms)
    ✓ Transfer emits event (97ms)
    ✓ Can not transfer above the amount
    ✓ Can not transfer from empty account
    ✓ Calls totalSupply on BasicToken contract
    ✓ Calls balanceOf with sender address on BasicToken contract


  7 passing (2s)

✨  Done in 8.88s.

可以看到,我们的测试成功了。

五、完整package.json

这里放出完整package.json,方便大家对照一下。

{
    
    
  "name": "waffle_test",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    
    
    "@openzeppelin/contracts": "^3.2.0",
    "@types/chai": "^4.2.14",
    "@types/mocha": "^8.0.4",
    "chai": "^4.2.0",
    "ethereum-waffle": "^3.2.0",
    "mocha": "^8.2.1",
    "ts-node": "^9.0.0",
    "typescript": "^4.0.5"
  },
  "mocha":{
    
    
    "extension": ["ts"],
    "spec": "./test/**/*.test.ts",
    "require": "ts-node/register",
    "timeout": 12000
  },  
  "scripts": {
    
    
    "build": "waffle",
    "test": "export NODE_ENV=test && mocha"
  }
}

六、总结

总结一下,Waffle使用还是挺简单的,但对照其官方文档操作会出现一些坑,也花了一些时间来解决。主要是因为官方文档介绍的流程是有一定的前提条件的,不是从零开始详细介绍其操作过程。另外软件版本的更新也引起了一些问题。

欢迎大家留言指正错误或者提出改进意见。

猜你喜欢

转载自blog.csdn.net/weixin_39430411/article/details/109612800