一、什么是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使用Mocha
和Chai
进行测试,所以必须先安装它们:
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"
的问题。
解决该问题的办法有两种:
- 将
mocha
的版本回退,比如设置为"^7.1.2"
,然后再运行yarn
来安装该版本的mocha
,这样就可以读取配置文件了。 - 正如前面提到的,在
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
使用还是挺简单的,但对照其官方文档操作会出现一些坑,也花了一些时间来解决。主要是因为官方文档介绍的流程是有一定的前提条件的,不是从零开始详细介绍其操作过程。另外软件版本的更新也引起了一些问题。
欢迎大家留言指正错误或者提出改进意见。