Getting Started

Installation

To get started, install ethereum-waffle:

yarn add --dev ethereum-waffle
npm install --save-dev ethereum-waffle

Add external dependency

Add an external library by installing it with yarn or npm:

yarn add @openzeppelin/contracts -D
npm install @openzeppelin/contracts -D

Writing a contract

Below is an example contract written in Solidity. Place it in src/BasicToken.sol file of your project:

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);
  }
}

Compiling the contract

Add the following entry in the package.json of your project :

Note

Since Waffle 3.0.0 it recognises waffle.json as default configuration file. If your configuration file is called waffle.json, it’s possible to use just waffle to build contracts.

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

In the waffle.json file of your project add the following entry:

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

Then run the following command:

yarn build
npm run build

You should see that Waffle compiled your contract and placed the resulting JSON output inside the build directory.

If you want to know more about how to configure Waffle, see Configuration.

Flattener

To flat your smart contracts run:

npx waffle flatten

In configuration file you can add optional field with path to flatten files:

{
  "flattenOutputDirectory": "./custom_flatten"
}

Writing tests

After you have successfully authored a Smart Contract you can now think about testing it. Fortunately for you, Waffle is packed with tools that help with that.

Tests in waffle are written using Mocha alongside with Chai. You can use a different test environment, but Waffle matchers only work with chai.

Run:

yarn add --dev mocha chai
npm install --save-dev mocha chai

Note

If you are using Typescript don’t forget to add ts-node and typescript to your devDependencies. Also, make sure to add a tsconfig.json, and within it, set "esModuleInterop" and "resolveJsonModule" to true. Lastly, instead of using mocha in your Node scripts within your package.json, replace it with ts-node.

Below is an example test file for the contract above written with Waffle. Place it under test/BasicToken.test.ts file in your project directory:

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]);
  });
});

Running tests

Update your package.json file to include:

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

If you are using TypeScript add mocha.opts file in your test folder:

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

And run:

yarn test
npm test

You should see the following output:

BasicToken
  ✓ Assigns initial balance (67ms)
  ✓ Transfer adds amount to destination account (524ms)
  ✓ Transfer emits event (309ms)
  ✓ Can not transfer above the amount (44ms)
  ✓ Can not transfer from empty account (78ms)
  ✓ Calls totalSupply on BasicToken contract (43ms)
  ✓ Calls balanceOf with sender address on BasicToken contract (45ms)


7 passing (5s)

If you want to know more about testing with Waffle, see Basic testing.