# Waffle Documentation

Waffle is a library for writing and testing smart contracts.

Sweeter, simpler and faster than Truffle.

Works with ethers-js.

## Philosophy:

- Simpler: Minimalistic, few dependencies.

- Sweeter: Nice syntax, easy to extend.

- Faster: Focus on the speed of tests execution.

## Features:

- Sweet set of chai matchers,

- Easy contract importing from npm modules,

- Fast compilation with native and dockerized solc,

- Typescript compatible,

- Fixtures that help write fast and maintainable test suites,

- Well documented.

## Versions and ethers compatibility

- Use version `0.2.3` with ethers 3 and solidity 4,

- Use version `1.2.0` with ethers 4 and solidity 4,

- Use version `2.*.*` with ethers 4, solidity 4, 5 and ability to use native or dockerized solc.

- Use version `3.*.*` with ethers 5, solidity 4, 5, 6 and ability to use native, dockerized solc or dockerized vyper.

### 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@^4.6.0 -D

    npm install @openzeppelin/contracts@^4.6.0 -D

#### Writing a contract

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

    // SPDX-License-Identifier: MIT

    pragma solidity ^0.8.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") {
          _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.8.13",
      "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`, `typescript` and `@types/mocha` to your `devDependencies`. Also, make sure to add a `tsconfig.json`, and within it, set `"esModuleInterop"` and `"resolveJsonModule"` to `true`.

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": "NODE_ENV=test mocha",
      }
    }

Next, add a `.mocharc.json` file in your root directory:

    {
      "spec": "test/**/*.test.{js,ts}"
    }

    {
      "require": "ts-node/register/transpile-only",
      "spec": "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.

### Compilation

#### Using third party libraries

One of the nice things about Waffle is that it enables you to import third party libraries when writing your smart contracts. All you need to do is install the library from `npm`.

For example you can install the popular `@openzeppelin/contracts` package:

    yarn add @openzeppelin/contracts

    npm install @openzeppelin/contracts

After installing a library, you can import it into your Solidity code:

    pragma solidity ^0.6.0;

    import "@openzeppelin/contracts/token/ERC721/ERC721Full.sol";

If you are using a custom `node_modules` location you can configure Waffle to recognize it. Change the `nodeModulesDirectory` setting in your `.waffle.json` file:

    {
      "nodeModulesDirectory": "path/to/node_modules"
    }

To read more about configuring Waffle, see Configuration.

#### Reducing compile times

By default, Waffle uses solc-js for compiling smart contracts. The package provides JavaScript bindings for the Solidity compiler. It is slow, but easy to use and install in the JS ecosystem.

Because we value speed and flexibility, we provide some alternatives that you can use with Waffle. There are two other options:

1. Installing solc directly on your computer, see Using native solc

2. Using solc installed in a docker container, see Using dockerized solc

#### Using native solc

This is the fastest option but comes with some downsides. The system wide installation means that you are stuck with a single Solidity version across all of your projects. Additionally it might be complicated to install the old versions of the compiler using this method.

We recommend this option if you only care about the latest solidity version.

You can find detailed installation instructions for native `solc` in the Solidity documentation.

Note

You need to install version compatible with your source files.

Change the `compilerType` setting in your `.waffle.json` file:

    {
      "compilerType": "native"
    }

To read more about configuring Waffle, see Configuration.

When compiling your smart contracts, Waffle will now use the native solc installation.

#### Using dockerized solc

This is the recommended option if you want flexibility when it comes to the compiler version. It is pretty easy to set up, especially if you have Docker installed.

If you don’t have docker visit the Docker documentation to learn how to install it.

After you’ve installed docker you can install the Solidity compiler. Pull the docker container tagged with the version you are interested in, for example for version 0.4.24:

    docker pull ethereum/solc:0.4.24

Then, change the `compilerType` setting in your `.waffle.json` file:

    {
      "compilerType": "dockerized-solc",
      "compilerVersion": "0.4.24"
    }

If no `compilerVersion` is specified the docker tag pulled defaults to `latest`. To read more about configuring Waffle, see Configuration.

When compiling your smart contracts, Waffle will now use the docker image you pulled.

#### Using dockerized vyper

This is the option if you have contracts in Vyper. You will need Docker installed.

To install docker visit the Docker documentation to learn how to do it.

To install dockerized Vyper pull the docker container tagged with the version you are interested in, for example for version 0.1.0:

    docker pull vyperlang/vyper:0.1.0

Then, change the `compilerType` setting in your `.waffle.json` file:

    {
      "compilerType": "dockerized-vyper",
      "compilerVersion": "0.1.0"
    }

If no `compilerVersion` is specified the docker tag pulled defaults to `latest`. To read more about configuring Waffle, see Configuration.

When compiling your smart contracts, Waffle will now use the docker image you pulled.

### Basic testing

#### Creating a provider

Creating a mock provider for your tests is super simple.

    import { MockProvider } from 'ethereum-waffle';
    const provider = new MockProvider();

This class takes an optional `MockProviderOptions` parameter in the constructor. Then the `ganacheOptions` from `MockProviderOptions` are passed to the underlying ganache-core implementation. You can read more about the options here.

#### Getting wallets

To obtain wallets that have been prefunded with eth use the provider

    import { MockProvider } from 'ethereum-waffle';

    const provider = new MockProvider();
    const [wallet, otherWallet] = provider.getWallets();

    // or use a shorthand

    const [wallet, otherWallet] = new MockProvider().getWallets();

By default this method returns 10 wallets. You can modify the returned wallets, by changing MockProvider configuration.

    import { MockProvider } from 'ethereum-waffle';
    const provider = new MockProvider({
        ganacheOptions: {
          accounts: [{balance: 'BALANCE IN WEI', secretKey: 'PRIVATE KEY'}]
        }
    });
    const wallets = provider.getWallets();

    import { MockProvider } from 'ethereum-waffle';
    const provider = new MockProvider({
      accounts: [{balance: 'BALANCE IN WEI', secretKey: 'PRIVATE KEY'}]
    });
    const wallets = provider.getWallets();

You can also get an empty random wallet by calling:

    import { MockProvider } from 'ethereum-waffle';
    const provider = new MockProvider();
    const wallet = provider.createEmptyWallet();

#### Deploying contracts

Once you compile your contracts using waffle, you can deploy them in your javascript code. It accepts three arguments:

  - wallet to send the deploy transaction

  - contract information (abi and bytecode)

  - contract constructor arguments

Deploy a contract:

    import BasicTokenMock from "build/BasicTokenMock.json";

    token = await deployContract(wallet, BasicTokenMock, [wallet.address, 1000]);

The contract information can be one of the following formats:

    interface StandardContractJSON {
      abi: any;
      evm: {bytecode: {object: any}};
    }

    interface SimpleContractJSON {
      abi: any[];
      bytecode: string;
    }

#### Linking

Link a library:

    myLibrary = await deployContract(wallet, MyLibrary, []);
    link(LibraryConsumer, 'contracts/MyLibrary.sol:MyLibrary', myLibrary.address);
    libraryConsumer = await deployContract(wallet, LibraryConsumer, []);

Note

You need to use a fully qualified name as the second parameter of the link function (path to the file relative to the root of the project, followed by a colon and the contract name).

### Chai matchers

A set of sweet chai matchers, makes your test easy to write and read. Before you can start using the matchers, you have to tell chai to use the solidity plugin:

    import chai from "chai";
    import { solidity } from "ethereum-waffle";

    chai.use(solidity);

Below is the list of available matchers:

#### Bignumbers

Testing equality of big numbers:

    expect(await token.balanceOf(wallet.address)).to.equal(993);

Available matchers for BigNumbers are: equal, eq, above, gt, gte, below, lt, lte, least, most, within, closeTo.

    expect(BigNumber.from(100)).to.be.within(BigNumber.from(99), BigNumber.from(101));
    expect(BigNumber.from(100)).to.be.closeTo(BigNumber.from(101), 10);

#### Emitting events

Testing what events were emitted with what arguments:

Note

You must `await` the `expect` in order for the matcher to execute properly. Failing to `await` will cause the assertion to pass whether or not the event is actually emitted!

    await expect(token.transfer(walletTo.address, 7))
      .to.emit(token, 'Transfer')
      .withArgs(wallet.address, walletTo.address, 7);

Note

The matcher will match `indexed` event parameters of type `string` or `bytes` even if the expected argument is not hashed using `keccack256` first.

Testing with indexed bytes or string parameters. These two examples are equivalent

    await expect(contract.addAddress("street", "city"))
      .to.emit(contract, 'AddAddress')
      .withArgs("street", "city");

    const hashedStreet = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("street"));
    const hashedCity = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("city"));
    await expect(contract.addAddress(street, city))
      .to.emit(contract, 'AddAddress')
      .withArgs(hashedStreet, hashedCity);

If you are using Waffle version `3.4.4` or lower, you can’t chain `emit` matchers like in example below.

    await expect(tx)
      .to.emit(contract, 'One')
      .to.emit(contract, 'Two');

This feature is available in Waffle version 4.

Testing the argument names in the event:

    await expect(token.transfer(walletTo.address, 7))
      .to.emit(token, 'Transfer')
      .withNamedArgs({
        from: wallet.address,
        to: walletTo.address,
        amount: 7
      });

A subset of arguments in an event can be tested by only including the desired arguments:

    await expect(token.transfer(walletTo.address, "8000000000000000000"))
      .to.emit(token, 'Transfer')
      .withNamedArgs({
        amount: "8000000000000000000"
      });

This feature will be available in Waffle version 4.

Events declared and emitted in a library:

If your contract is using a Solidity `Library` that declares and emits events and you want to test your contract for emitting those events, you’ll have to specifically point to the library. You can use `attach` method on the library object. An example snippet for this case:

    await expect(contract.function())
      .to.emit(library.attach(contract.address), 'MyEvent');

where `library` is the ethers object of the Solidity Library in which `MyEvent` is declared.

#### Called on contract

Testing if function was called on the provided contract:

    await token.balanceOf(wallet.address)

    expect('balanceOf').to.be.calledOnContract(token);

#### Called on contract with arguments

Testing if function with certain arguments was called on provided contract:

    await token.balanceOf(wallet.address)

    expect('balanceOf').to.be.calledOnContractWith(token, [wallet.address]);

#### Revert

Testing if transaction was reverted:

    await expect(token.transfer(walletTo.address, 1007)).to.be.reverted;

#### Revert with message

Testing if transaction was reverted with certain message:

    await expect(token.transfer(walletTo.address, 1007))
      .to.be.revertedWith('Insufficient funds');

You can also test if revert message matches to a regular expression:

    await expect(token.checkRole('ADMIN'))
      .to.be.revertedWith(/AccessControl: account .* is missing role .*/);

#### Change ether balance

Testing whether the transaction changes the balance of the account:

    await expect(() => wallet.sendTransaction({to: walletTo.address, value: 200}))
      .to.changeEtherBalance(walletTo, 200);

    await expect(await wallet.sendTransaction({to: walletTo.address, value: 200}))
      .to.changeEtherBalance(walletTo, 200);

`expect` for `changeEtherBalance` gets one of the following parameters:

> - transaction call : () => Promise<TransactionResponse>

> - transaction response : TransactionResponse

Note

`changeEtherBalance` won’t work if there is more than one transaction mined in the block.

The transaction call should be passed to the `expect` as a callback (we need to check the balance before the call) or as a transaction response.

The matcher can accept numbers, strings and BigNumbers as a balance change, while the account should be specified either as a Wallet or a Contract.

`changeEtherBalance` ignores transaction fees by default:

    // Default behavior
    await expect(await wallet.sendTransaction({to: walletTo.address, value: 200}))
      .to.changeEtherBalance(wallet, -200);

    // To include the transaction fee use:
    await expect(await wallet.sendTransaction({to: walletTo.address, gasPrice: 1, value: 200}))
      .to.changeEtherBalance(wallet, -21200, {includeFee: true});

Note

`changeEtherBalance` calls should not be chained. If you need to check changes of the balance for multiple accounts, you should use the `changeEtherBalances` matcher.

#### Change ether balance (multiple accounts)

Testing whether the transaction changes balance of multiple accounts:

    await expect(() => wallet.sendTransaction({to: walletTo.address, value: 200}))
      .to.changeEtherBalances([wallet, walletTo], [-200, 200]);

    await expect(await wallet.sendTransaction({to: walletTo.address, value: 200}))
      .to.changeEtherBalances([wallet, walletTo], [-200, 200]);

Note

`changeEtherBalances` calls won’t work if there is more than one transaction mined in the block.

#### Change token balance

Testing whether the transfer changes the balance of the account:

    await expect(() => token.transfer(walletTo.address, 200))
      .to.changeTokenBalance(token, walletTo, 200);

    await expect(() => token.transferFrom(wallet.address, walletTo.address, 200))
      .to.changeTokenBalance(token, walletTo, 200);

Note

The transfer call should be passed to the `expect` as a callback (we need to check the balance before the call).

The matcher can accept numbers, strings and BigNumbers as a balance change, while the account should be specified either as a Wallet or a Contract.

Note

`changeTokenBalance` calls should not be chained. If you need to check changes of the balance for multiple accounts, you should use the `changeTokenBalances` matcher.

#### Change token balance (multiple accounts)

Testing whether the transfer changes balance for multiple accounts:

    await expect(() => token.transfer(walletTo.address, 200))
      .to.changeTokenBalances(token, [wallet, walletTo], [-200, 200]);

#### Proper address

Testing if a string is a proper address:

    expect('0x28FAA621c3348823D6c6548981a19716bcDc740e').to.be.properAddress;

#### Proper private key

Testing if a string is a proper private key:

    expect('0x706618637b8ca922f6290ce1ecd4c31247e9ab75cf0530a0ac95c0332173d7c5').to.be.properPrivateKey;

#### Proper hex

Testing if a string is a proper hex value of given length:

    expect('0x70').to.be.properHex(2);

#### Hex Equal

Testing if a string is a proper hex with value equal to the given hex value. Case insensitive and strips leading zeros:

    expect('0x00012AB').to.hexEqual('0x12ab');

### Deprecated matchers

#### Change balance

Deprecated since version 3.1.2: Use `changeEtherBalance()` instead.

Testing whether the transaction changes the balance of the account:

    await expect(() => wallet.sendTransaction({to: walletTo.address, gasPrice: 0, value: 200}))
      .to.changeBalance(walletTo, 200);

    await expect(await wallet.sendTransaction({to: walletTo.address, gasPrice: 0, value: 200}))
      .to.changeBalance(walletTo, 200);

`expect` for `changeBalance` gets one of the following parameters:

> - transaction call : () => Promise<TransactionResponse>

> - transaction response : TransactionResponse

Note

`changeBalance` won’t work if there is more than one transaction mined in the block.

The transaction call should be passed to the `expect` as a callback (we need to check the balance before the call) or as a transaction response.

The matcher can accept numbers, strings and BigNumbers as a balance change, while the account should be specified either as a Wallet or a Contract.

Note

`changeBalance` calls should not be chained. If you need to check changes of the balance for multiple accounts, you should use the `changeBalances` matcher.

#### Change balance (multiple accounts)

Deprecated since version 3.1.2: Use `changeEtherBalances()` instead.

Testing whether the transaction changes balance of multiple accounts:

    await expect(() => wallet.sendTransaction({to: walletTo.address, gasPrice: 0, value: 200}))
      .to.changeBalances([wallet, walletTo], [-200, 200]);

    await expect(await wallet.sendTransaction({to: walletTo.address, gasPrice: 0, value: 200}))
      .to.changeBalances([wallet, walletTo], [-200, 200]);

Note

`changeBalances` calls won’t work if there is more than one transaction mined in the block.

### Fixtures

When testing code dependent on smart contracts, it is often useful to have a specific scenario play out before every test. For example, when testing an ERC20 token, one might want to check whether each and every address can or cannot perform transfers. Before each of those tests however, you have to deploy the ERC20 contract and maybe transfer some funds.

The repeated deployment of contracts might slow down the test significantly. This is why Waffle allows you to create fixtures - testing scenarios that are executed once and then remembered by making snapshots of the blockchain. This speeds up the tests considerably.

Example:

    import {expect} from 'chai';
    import {loadFixture, deployContract} from 'ethereum-waffle';
    import BasicTokenMock from './build/BasicTokenMock';

    describe('Fixtures', () => {
      async function fixture([wallet, other], provider) {
        const token = await deployContract(wallet, BasicTokenMock, [
          wallet.address, 1000
        ]);
        return {token, wallet, other};
      }

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

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

    import {expect} from 'chai';
    import {loadFixture, deployContract} from 'ethereum-waffle';
    import BasicTokenMock from './build/BasicTokenMock';

    describe('Fixtures', () => {
      async function fixture(provider, [wallet, other]) {
        const token = await deployContract(wallet, BasicTokenMock, [
          wallet.address, 1000
        ]);
        return {token, wallet, other};
      }

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

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

Fixtures receive a provider and an array of wallets as an argument. By default, the wallets are obtained by calling `getWallets` and the provider by `createMockProvider`. You can, however, override those by using a custom fixture loader.

    import {createFixtureLoader} from 'ethereum-waffle';

    const loadFixture = createFixtureLoader(myWallets, myProvider);

    // later in tests
    await loadFixture((myWallets, myProvider) => {
      // fixture implementation
    });

    import {createFixtureLoader} from 'ethereum-waffle';

    const loadFixture = createFixtureLoader(myProvider, myWallets);

    // later in tests
    await loadFixture((myProvider, myWallets) => {
      // fixture implementation
    });

### Mock contract

Mocking your smart contract dependencies.

#### Usage

Create an instance of a mock contract providing the `ABI` of the smart contract you want to mock:

    import {deployMockContract} from '@ethereum-waffle/mock-contract';

    ...

    const mockContract = await deployMockContract(wallet, contractAbi);

You can also choose the deployment address of the mock contract with the options argument:

    const mockContract = await deployMockContract(wallet, contractAbi, {
      address: deploymentAddress,
      overrride: false // optional, specifies if the contract should be overwritten
    })

The mock contract can now be integrated into other contracts by using the `address` attribute. Return values for mocked functions can be set using:

    await mockContract.mock.<nameOfMethod>.returns(<value>)
    await mockContract.mock.<nameOfMethod>.withArgs(<arguments>).returns(<value>)

Methods can also be set up to be reverted using:

    await mockContract.mock.<nameOfMethod>.reverts()
    await mockContract.mock.<nameOfMethod>.revertsWithReason(<reason>)
    await mockContract.mock.<nameOfMethod>.withArgs(<arguments>).reverts()
    await mockContract.mock.<nameOfMethod>.withArgs(<arguments>).revertsWithReason(<reason>)

#### Example

The example below illustrates how `mock-contract` can be used to test the very simple `AmIRichAlready` contract.

    pragma solidity ^0.6.0;

    interface IERC20 {
        function balanceOf(address account) external view returns (uint256);
    }

    contract AmIRichAlready {
        IERC20 private tokenContract;
        uint private constant RICHNESS = 1000000 * 10 ** 18;

        constructor (IERC20 _tokenContract) public {
            tokenContract = _tokenContract;
        }

        function check() public view returns (bool) {
            uint balance = tokenContract.balanceOf(msg.sender);
            return balance > RICHNESS;
        }
    }

We are mostly interested in the `tokenContract.balanceOf` call. Mock contract will be used to mock exactly this call with values that are relevant for the return of the `check()` method.

    const {use, expect} = require('chai');
    const {ContractFactory, utils} = require('ethers');
    const {MockProvider} = require('@ethereum-waffle/provider');
    const {waffleChai} = require('@ethereum-waffle/chai');
    const {deployMockContract} = require('@ethereum-waffle/mock-contract');

    const IERC20 = require('../build/IERC20');
    const AmIRichAlready = require('../build/AmIRichAlready');

    use(waffleChai);

    describe('Am I Rich Already', () => {
      async function setup() {
        const [sender, receiver] = new MockProvider().getWallets();
        const mockERC20 = await deployMockContract(sender, IERC20.abi);
        const contractFactory = new ContractFactory(AmIRichAlready.abi, AmIRichAlready.bytecode, sender);
        const contract = await contractFactory.deploy(mockERC20.address);
        return {sender, receiver, contract, mockERC20};
      }

      it('returns false if the wallet has less then 1000000 coins', async () => {
        const {contract, mockERC20} = await setup();
        await mockERC20.mock.balanceOf.returns(utils.parseEther('999999'));
        expect(await contract.check()).to.be.equal(false);
      });

      it('returns true if the wallet has more than 1000000 coins', async () => {
        const {contract, mockERC20} = await setup();
        await mockERC20.mock.balanceOf.returns(utils.parseEther('1000001'));
        expect(await contract.check()).to.equal(true);
      });

      it('reverts if the ERC20 reverts', async () => {
        const {contract, mockERC20} = await setup();
        await mockERC20.mock.balanceOf.reverts();
        await expect(contract.check()).to.be.revertedWith('Mock revert');
      });

      it('returns 1000001 coins for my address and 0 otherwise', async () => {
        const {contract, mockERC20, sender, receiver} = await setup();
        await mockERC20.mock.balanceOf.returns('0');
        await mockERC20.mock.balanceOf.withArgs(sender.address).returns(utils.parseEther('1000001'));

        expect(await contract.check()).to.equal(true);
        expect(await contract.connect(receiver.address).check()).to.equal(false);
      });
    });

#### Mocking multiple calls

Mock contract allows to queue multiple mock calls to the same function. This can only be done if the function is not pure or view. That’s because the mock call queue is stored on the blockchain and we need to modify it.

    await mockContract.mock.<nameOfMethod>.returns(<value1>).returns(<value2>);

    await mockContract.<nameOfMethod>() // returns <value1>
    await mockContract.<nameOfMethod>() // returns <value2>

Just like with regular mock calls, the queue can be set up to revert or return a specified value. It can also be set up to return different values for different arguments.

    await mockContract.mock.<nameOfMethod>.returns(<value1>).returns(<value2>);
    await mockContract.mock.<nameOfMethod>.withArgs(<arguments1>).returns(<value3>);

    await mockContract.<nameOfMethod>() // returns <value1>
    await mockContract.<nameOfMethod>() // returns <value2>
    await mockContract.<nameOfMethod>(<arguments1>) // returns <value3>

Keep in mind that the mocked revert must be at the end of the queue, because it prevents the contract from updating the queue.

    await mockContract.mock.<nameOfMethod>.returns(<value1>).returns(<value2>).reverts();

    await mockContract.<nameOfMethod>() // returns <value1>
    await mockContract.<nameOfMethod>() // returns <value2>
    await mockContract.<nameOfMethod>() // reverts

When the queue is empty, the mock contract will return the last value from the queue and each time the you set up a new queue, the old one is overwritten.

    await mockContract.mock.<nameOfMethod>.returns(<value1>).returns(<value2>);

    await mockContract.<nameOfMethod>() // returns <value1>
    await mockContract.<nameOfMethod>() // returns <value2>
    await mockContract.<nameOfMethod>() // returns <value2>

    await mockContract.mock.<nameOfMethod>.returns(<value1>).returns(<value2>);
    await mockContract.mock.<nameOfMethod>.returns(<value3>).returns(<value4>);

    await mockContract.<nameOfMethod>() // returns <value3>
    await mockContract.<nameOfMethod>() // returns <value4>

#### Mocking receive function

The `receive` function of the mocked Smart Contract can be mocked to revert. It cannot however be mocked to return a specified value, because of gas limitations when calling another contract using `send` and `transfer`.

##### Receive mock example

    pragma solidity ^0.6.0;

    interface IERC20 {
        function balanceOf(address account) external view returns (uint256);
        fallback() external payable;
        receive() external payable;
    }

    contract EtherForward {
        IERC20 private tokenContract;

        constructor (IERC20 _tokenContract) public {
            tokenContract = _tokenContract;
        }

        function forward() public payable {
            payable(tokenContract).transfer(msg.value);
        }
    }

    (...)

    it('use the receive function normally', async () => {
      const {contract, mockERC20} = await setup();

      expect (
        await mockERC20.provider.getBalance(mockERC20.address)
      ).to.be.equal(0);

      await contract.forward({value: 7})

      expect (
        await mockERC20.provider.getBalance(mockERC20.address)
      ).to.be.equal(7);
    });

    it('can mock the receive function to revert', async () => {
      const {contract, mockERC20} = await setup();

      await mockERC20.mock.receive.revertsWithReason('Receive function rejected')

      await expect(
        contract.forward({value: 7})
      ).to.be.revertedWith('Receive function rejected')

      expect (
        await mockERC20.provider.getBalance(mockERC20.address)
      ).to.be.equal(0);
    });

    (...)

### ENS

#### Dependencies

Make sure to install additional peer dependencies needed to use Waffle ENS tools.

    yarn add --dev @ensdomains/ens@^0.4.4 @ensdomains/resolver@^0.2.4

    npm install --save-dev @ensdomains/ens@^0.4.4 @ensdomains/resolver@^0.2.4

##### Creating a ENS

Creating a simple ENS for testing with `ENS`.

    import {MockProvider} from '@ethereum-waffle/provider';
    import {deployENS, ENS} from '@ethereum-waffle/ens';

    const provider = new MockProvider();
    const [wallet] = provider.getWallets();
    const ens: ENS = await deployENS(wallet);

This class takes a `wallet` in the constructor. The `wallet` available in `MockProvider` class in package `@ethereum-waffle/provider`.

##### Setup ENS

Note

The feature was introduced in Waffle 3.0.0

Also, if you use `MockProvider`, you can use `setupENS()` function in `MockProvider`, to create and setup simple `ENS`.

    import {MockProvider} from '@ethereum-waffle/provider';

    const provider = new MockProvider();
    await provider.setupENS();
    await provider.ens.createTopLevelDomain('test');

`setupENS()` method employs the last of the provider’s `wallets` by default, but you can pass your own `wallet` as an argument for `setupENS(wallet)`.

Also `setupENS()` method saves ENS address in the provider’s networks.

##### Creating top level domain

Use `createTopLevelDomain` function to create a top level domain:

    await ens.createTopLevelDomain('test');

##### Creating sub domain

Use `createSubDomain` function for creating a sub domain:

    await ens.createSubDomain('ethworks.test');

##### Creating sub domain with options

Note

The feature was introduced in Waffle 3.0.0

It is also possible to create a sub domain recursively, if the top domain doesn’t exist, by specifying the appropriate option:

    await ens.createSubDomain('waffle.ethworks.tld', {recursive: true});

##### Setting address

Use `setAddress` function for setting address for the domain:

    await ensBuilder.setAddress('vlad.ethworks.test', '0x001...03');

##### Setting address with options

Note

The feature was introduced in Waffle 3.0.0

It is also possible to set an address for domain recursively, if the domain doesn’t exist, by specifying the appropriate option:

    await ens.setAddress('vlad.waffle.ethworks.tld', '0x001...03', {recursive: true});

Use `setAddressWithReverse` function for setting address for the domain and make this domain reverse. Add recursive option if the domain doesn’t exist:

    await ens.setAddressWithReverse('vlad.ethworks.tld', wallet, {recursive: true});

### Configuration

#### Configuration file

While Waffle works well enough without any configurations, advanced users might want to excert more control over what happens when they use Waffle in their projects.

This is why we made it very easy to configure Waffle to meet your needs. All you need to do is create a `waffle.json` file inside your project and point waffle to it.

First create your `waffle.json` configuration file:

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

Note

Each configuration is optional.

Afterwards update your `package.json` build script:

Note

Since Waffle 3.0.0 it recognises `waffle.json` as the 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"
      }
    }

Configuration options:

- sourceDirectory

- outputDirectory

- nodeModulesDirectory

- cacheDirectory

- compilerType

- compilerVersion

- compilerAllowedPaths

- compilerOptions

- outputHumanReadableAbi

- outputType

##### sourceDirectory

You can specify a custom path to the directory containing your smart contracts. Waffle uses `./contracts` as the default value for `sourceDirectory`. The path you provide will be resolved relative to the current working directory.

Example:

    {
      "sourceDirectory": "./custom/path/to/contracts"
    }

##### outputDirectory

You can specify a custom path to the directory to which Waffle saves the compilation output. Waffle uses `./build` as the default value for `outputDirectory`. The path you provide will be resolved relative to the current working directory.

Example:

    {
      "outputDirectory": "./custom/path/to/output"
    }

##### nodeModulesDirectory

You can specify a custom path to the `node_modules` folder which Waffle will use to resolve third party dependencies. Waffle uses `./node_modules` as the default value for `nodeModulesDirectory`. The path you provide will be resolved relative to the current working directory.

For more information about third party libraries, see Using third party libraries.

Example:

    {
      "nodeModulesDirectory": "./custom/path/to/node_modules"
    }

##### cacheDirectory

When compiling using `solcjs` and using a non-default `compilerVersion` Waffle downloads the necessary `solcjs` binary from a remote server. This file is cached to speed up subsequent runs. You can specify a custom path to the directory in which caches are saved. Waffle uses `./cache` as the default value for `cacheDirectory`. The path you provide will be resolved relative to the current working directory.

Example:

    {
      "cacheDirectory": "./custom/path/to/cache"
    }

##### compilerType

Specifies the compiler to use. For more information see: Reducing compile times. Allowed values:

> - `solcjs`

> - `native`

> - `dockerized-solc`

> - `dockerized-vyper`

Waffle uses `solcjs` as the default value for `compilerType`.

Example:

    {
      "compilerType": "dockerized-solc"
    }

##### compilerVersion

Specifies the version of the compiler. Should be a semver string like `0.5.9`. You can use it with `"compilerType": "solcjs"` or `"compilerType": "dockerized-solc"`.

When using `"compilerType": "solcjs"` you can also specify the exact commit that will be used or a path to a specific solc module dependency.

To find a specific commit please consult the list of available solc versions.

Examples:

    {
      "compilerType": "dockerized-solc",
      "compilerVersion": "0.4.24"
    }

    {
      "compilerType": "solcjs",
      "compilerVersion": "v0.4.24+commit.e67f0147"
    }

    {
      "compilerType": "solcjs",
      "compilerVersion": "./node_modules/solc"
    }

##### compilerAllowedPaths

The `solc` compiler has restrictions on paths it can access for security reasons. The value of `compilerAllowedPaths` will be passed as a command line argument: `solc --allow-paths <VALUE>`.

This is especially useful if you are doing a monorepo setup with Lerna, see: Usage with Lernajs.

Example:

    {
      "compilerAllowedPaths": ["../contracts"]
    }

##### compilerOptions

You can customize the behaviour of `solc` by providing custom settings for it. All of the information is provided in the Solidity documentation. Value of the `compilerOptions` configuration setting will be passed to `solc` as `settings`.

For detailed list of options go to solidity documentation (sections: ‘Setting the EVM version to target’, ‘Target options’ and ‘Compiler Input and Output JSON Description’).

Example:

    {
      "compilerOptions": {
        "evmVersion": "constantinople"
      }
    }

##### outputType

See: KLAB compatibility.

##### outputHumanReadableAbi

Waffle supports Human Readable Abi.

In order to enable its output, you need to set `outputHumanReadableAbi` to `true` in your config file:

    {
      "outputHumanReadableAbi": true
    }

For the compiled contracts you will now see the following in the output:

    {
      "humanReadableAbi": [
        "constructor(uint256 argOne)",
        "event Bar(bool argOne, uint256 indexed argTwo)",
        "event FooEvent()",
        "function noArgs() view returns(uint200)",
        "function oneArg(bool argOne)",
        "function threeArgs(string argOne, bool argTwo, uint256[] argThree) view returns(bool, uint256)",
        "function twoReturns(bool argOne) view returns(bool, uint256)"
      ]
    }

##### typechain

Waffle supports typechain artifacts generation. To enable typed artifacts generation you should set `typechainEnabled` property to `true`. You are also able to define target folder for your artifacts by defining `typechainOutputDir` property, which is set to `./types` by default. Property `typechainOutputDir` is a path relative to outputDirectory.

Example:

    {
      "typechainEnabled": true,
      "typechainOutputDir": "typechain"
    }

#### Other configuration file formats

Waffle supports the following configuration file formats:

JSON:

    {
      "sourceDirectory": "./src/contracts",
    }

JavaScript:

    module.exports = {
      sourceDirectory: './src/contracts'
    }

The configuration can even be a promise

    module.exports = Promise.resolve({
      sourceDirectory: './src/contracts'
    })

Hint

This is a powerful feature if you want to asynchronously load different compliation configurations in different environments. For example, you can use native solc in CI for faster compilation, while deciding the exact solc-js version locally based on the contract versions being used. Since many of those operations are asynchronous, you’ll most likely be returning a Promise to waffle to handle.

#### Setting Solidity compiler version

See compilerVersion.

#### Usage with Truffle

Waffle output should be compatible with Truffle by default.

#### Custom compiler options

See compilerOptions.

#### KLAB compatibility

The default compilation process is not compatible with KLAB (a formal verification tool, see: https://github.com/dapphub/klab). To compile contracts to work with KLAB one must:

1. Set appropriate compiler options, i.e.:

    compilerOptions: {
      outputSelection: {
        "*": {
          "*": [ "evm.bytecode.object", "evm.deployedBytecode.object",
                 "abi" ,
                 "evm.bytecode.sourceMap", "evm.deployedBytecode.sourceMap" ],

          "": [ "ast" ]
        },
      }
    }

2. Set appropriate output type. We support two types: one (default) generates a single file for each contract and the other (KLAB friendly) generates one file (Combined-Json.json) combining all contracts. The latter type does not meet (in contrary to the first one) all official solidity standards since KLAB requirements are slightly modified. Set the output in the config file:

Possible options are:

- ‘multiple’: a single file for each contract;

- ‘combined’: one KLAB friendly file;

- ‘all’: generates both above outputs;

- ‘minimal’: a single file for each contract with minimal information (just abi and bytecode).

Note

‘minimal’ option was introduced in Waffle 3.0.0.

    {
      "outputType": "combined"
    }

An example of full KLAB friendly config file:

    module.exports = {
      compilerType: process.env.WAFFLE_COMPILER,
      outputType: 'all',
      compilerOptions: {
        outputSelection: {
          "*": {
            "*": [ "evm.bytecode.object", "evm.deployedBytecode.object",
                   "abi" ,
                   "evm.bytecode.sourceMap", "evm.deployedBytecode.sourceMap" ],

            "": [ "ast" ]
          },
       }
     }
    };

#### Monorepo

Waffle works well with mono-repositories. It is enough to set up a common `nodeModulesDirectory` in the configuration file to make it work. We recommend using yarn workspaces and wsrun for monorepo management.

#### Usage with Lernajs

Waffle works with lerna, but requires additional configuration. When lerna cross-links npm packages in monorepo, it creates symbolic links to the original catalog. They lead to sources files located beyond allowed paths. This process breaks compilation with native solc.

If you see the following message in your monorepo setup:

    contracts/Contract.sol:4:1: ParserError: Source ".../monorepo/node_modules/YourProjectContracts/contracts/Contract.sol" not found: File outside of allowed directories.
    import "YourProjectContracts/contracts/Contract.sol";

you probably need to add allowedPath to your waffle configuration.

Assuming you have the following setup:

    /monorepo
      /YourProjectContracts
        /contracts
      /YourProjectDapp
        /contracts

Add to waffle configuration in YourProjectDapp:

    {
      "compilerAllowedPaths": ["../YourProjectContracts"]
    }

That should solve a problem.

Currently Waffle does not support a similar feature for dockerized solc.

### Migration guides

#### Migration from Waffle 2.2.0 to Waffle 2.3.0

##### Created monorepo

Waffle was internally migrated to a monorepo. Thanks to this, you can now use parts of waffle individually. We provide the following packages:

- `ethereum-waffle` - core package exporting everything

- `ethereum-compiler` - compile your contracts programmatically

- `ethereum-chai` - chai matchers for better unit testing

- `ethereum-provider` - mock provider to interact with an in-memory blockchain

##### Created MockProvider class

We added MockProvider class. It changed the creation of the provider.

Waffle 2.2.0

    await createMockProvider(options);

Waffle 2.3.0

    provider = new MockProvider();

##### Reorganise getWallets() method

Waffle 2.2.0

    await getWallets(provider);

Waffle 2.3.0

    new MockProvider().getWallets()

#### Migration from Waffle 2.3.0 to Waffle 2.4.0

##### Renamed configuration options

We renamed configuration options to compile contracts:

- `sourcesPath` - renamed to sourceDirectory

- `targetPath` - renamed to outputDirectory

- `npmPath` - renamed to nodeModulesDirectory

- `compiler` - renamed to compilerType

- `docker-tag` - replaced by compilerVersion

- `solcVersion` - replaced by compilerVersion

- `legacyOutput` - removed, setting it to false gave no effect

- `allowedPaths` - renamed to compilerAllowedPaths

- `ganacheOptions` - removed, wasn’t used by the compiler

#### Migration from Waffle 2.5.* to Waffle 3.0.0

There are some new functionality and some slight refactoring and improved paradigms in Waffle v3.

##### Removed deprecated APIs from the provider

In `Waffle 3.0.0` we remove deprecated APIs from the provider, such as `createMockProvider` and `getGanacheOptions`.

##### Swapped arguments for Fixture

In `Waffle 3.0.0` we swapped arguments for Fixture, because the provider argument is very rarely used compared to wallets. So such implementation should be more convenient for users.

Waffle 2.5.0

    function createFixtureLoader(overrideProvider?: MockProvider, overrideWallets?: Wallet[]);

Waffle 3.0.0

    function createFixtureLoader(overrideWallets?: Wallet[], overrideProvider?: MockProvider);

Waffle 2.5.0

    import {expect} from 'chai';
    import {loadFixture, deployContract} from 'ethereum-waffle';
    import BasicTokenMock from './build/BasicTokenMock';

    describe('Fixtures', () => {
      async function fixture(provider, [wallet, other]) {
        const token = await deployContract(wallet, BasicTokenMock, [
          wallet.address, 1000
        ]);
        return {token, wallet, other};
      }

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

Waffle 3.0.0

    import {expect} from 'chai';
    import {loadFixture, deployContract} from 'ethereum-waffle';
    import BasicTokenMock from './build/BasicTokenMock';

    describe('Fixtures', () => {
      async function fixture([wallet, other], provider) {
        const token = await deployContract(wallet, BasicTokenMock, [
          wallet.address, 1000
        ]);
        return {token, wallet, other};
      }

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

##### Added automatic recognising waffle.json config without cli argument

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

In Waffle 2.5.0, If the argument has not been provided, the Waffle uses the default configuration.

Waffle 2.5.0

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

Waffle 3.0.0

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

##### Introduced MockProviderOptions

We added MockProviderOptions. It will be convenient in the future, when the provider may need some options other than `ganacheOptions`.

Waffle 2.5.0

    import {expect} from 'chai';
    import {Wallet} from 'ethers';
    import {MockProvider} from 'ethereum-waffle';
    import {deployToken} from './BasicToken';

    describe('INTEGRATION: MockProvider', () => {
      it('accepts options', () => {
        const original = Wallet.createRandom();
        const provider = new MockProvider({
            accounts: [{balance: '100', secretKey: original.privateKey}]
        });
        const wallets = provider.getWallets();
        expect(wallets.length).to.equal(1);
        expect(wallets[0].address).to.equal(original.address);
      });
    });

Waffle 3.0.0

    import {expect} from 'chai';
    import {Wallet} from 'ethers';
    import {MockProvider} from 'ethereum-waffle';
    import {deployToken} from './BasicToken';

    describe('INTEGRATION: MockProvider', () => {
      it('accepts options', () => {
        const original = Wallet.createRandom();
        const provider = new MockProvider({
          ganacheOptions: {
            accounts: [{balance: '100', secretKey: original.privateKey}]
          }
        });
        const wallets = provider.getWallets();
        expect(wallets.length).to.equal(1);
        expect(wallets[0].address).to.equal(original.address);
      });
    });

##### Dropped support for contract interface

We dropped support for contract interface because it duplicated contract ABI. Also `interface` is a keyword in typescript, so we decided not to use this field. Now we support just `contract.abi`.

Waffle 2.5.0

    {
      "abi": [
        ...
      ],
      "interface: [
        ...
      ],
      "evm": {
        ...
      },
      "bytecode": "..."
    }

Waffle 3.0.0

    {
      "abi": [
        {...}
      ],
      "evm": {
        ...
      },
      "bytecode": "..."
    }

#### Migration from Waffle 3.4.0 to Waffle 4.0.0-alpha

##### Dependencies upgrades

The main difference between Waffle 3.4.0 and Waffle 4.0.0-alpha is about dependencies that Waffle packages use. We updated the following dependencies:

- `typechain` - bumped version from ^2.0.0 to ^9.0.0. Now every Waffle package uses the same version of the package. Also the package was moved to the `peerDependencies` section - you now need to install `typechain` manually when using Waffle.

- `ethers` - bumped version from to ^5.5.4. Now every Waffle package uses the same version of the package. Also the package was moved to the `peerDependencies` section - you now need to install `ethers` manually when using Waffle.

- `solc` - the package is used by `waffle-compiler` package to provide the default option for compiling Solidity code. Was moved to the `peerDependencies` section and has no version restrictions - you now have to install `solc` manually when using Waffle.

- Deprecated `ganache-core` package has been replaced with `ganache` version ^7.0.3. It causes slight differences in the parameters of `MockProvider` from `@ethereum-waffle/provider`. Now the `MockProvider` uses `berlin` hardfork by default.

##### Changes to `MockProvider` parameters

Previous (optional) parameters of `MockProvider` included override options for the Ganache provider:

    interface MockProviderOptions {
      ganacheOptions: {
        account_keys_path?: string;
        accounts?: object[];
        allowUnlimitedContractSize?: boolean;
        blockTime?: number;
        db_path?: string;
        debug?: boolean;
        default_balance_ether?: number;
        fork?: string | object;
        fork_block_number?: string | number;
        forkCacheSize?: number;
        gasLimit?: string | number;
        gasPrice?: string;
        hardfork?: "byzantium" | "constantinople" | "petersburg" | "istanbul" | "muirGlacier";
        hd_path?: string;
        locked?: boolean;
        logger?: {
          log(msg: string): void;
        };
        mnemonic?: string;
        network_id?: number;
        networkId?: number;
        port?: number;
        seed?: any;
        time?: Date;
        total_accounts?: number;
        unlocked_accounts?: string[];
        verbose?: boolean;
        vmErrorsOnRPCResponse?: boolean;
        ws?: boolean;
      }
    }

Current `ganacheOptions` parameter are documented here.

##### Typechain changes

If you used type generation (`typechainEnabled` option set to `true` in `waffle.json`), you need to update your code to conform to the new naming convention used by `typechain`. Contract factories now have postfix `__factory` instead of `Factory`. For example, `MyContractFactory` becomes `MyContract__factory`. Example refactoring:

    const contractConstructorArgs: [string, string] = [bob.address, charlie.address];
    -const contract = await deployContract(alice, MyContractFactory, contractConstructorArgs);
    +const contract = await deployContract(alice, MyContract__factory, contractConstructorArgs);

##### `@ethereum-waffle/jest` deprecated

We stopped supporting `@ethereum-waffle/jest`. From now on the package is deprecated and will be removed in the future. The suggested test framework to use with `Waffle` is `mocha` combined with `chai` and `@ethereum-waffle/chai` package. If you want to migrate from jest to mocha in your project, please follow the guide below.

1. Setup `mocha` - this may include the following steps:

> - installing `mocha` and `chai` packages.

> - if you are using `typescript`, installing `@types/mocha`, `@types/chai` and `ts-node` packages.

> - updating your `test` script (the common pattern for typescript is `"test": "mocha -r ts-node/register/transpile-only 'test/**/*.test.ts'"`).

> - updating your tests to use `mocha` syntax.

2. Replace `@ethereum-waffle/jest` with `@ethereum-waffle/chai`. Below is little table that contains the list of all the matchers provided by `@ethereum-waffle/jest` and their replacements in `@ethereum-waffle/chai`.

Matchers replacements
@ethereum-waffle/jest | @ethereum-waffle/chai
--- | ---
`.toChangeBalance(wallet, balanceChange)` | `.to.changeEtherBalance(wallet, balanceChange)`
`.toChangeBalances(wallets, balanceChanges)` | `.to.changeEtherBalances(wallets, balanceChanges)`
`.toBeGtBN(value)` | `.to.be.gt(value)`
`.toBeLtBN(value)` | `.to.be.lt(value)`
`.toBeGteBN(value)` | `.to.be.gte(value)`
`.toBeLteBN(value)` | `.to.be.lte(value)`
`.toEqBN(value)` | `.to.equal(value)`
`.toHaveEmitted(contract, eventName)` | `.to.emit(contract, eventName)`
`.toHaveEmittedWith(contract, eventName, args)` | `.to.emit(contract, eventName).withArgs(...args)`
`.toThrowErrorMatchingSnapshot()` | `.to.be.eventually.rejectedWith(expected, message)`
`.toBeProperPrivateKey()` | `.to.be.properPrivateKey`
`.toBeProperAddress()` | `.to.be.properAddress`
`.toBeReverted()` | `.to.be.reverted`
`.toBeRevertedWith(revertReason)` | `.to.be.revertedWith(reason)`

##### Time-based tests

If your tests rely on manually setting timestamp on the blockchain using evm_mine, you need to use evm_setTime alongside.

For example:

    export async function mineBlock(provider: providers.Web3Provider, timestamp: number) {
      +provider.send('evm_setTime', [timestamp * 1000])
      await provider.send('evm_mine', [timestamp])
    }

##### Tests relying on setting gasPrice

Since London hardfork, `baseFeePerGas` is replacing `gasPrice`. If your tests are relying on setting `gasPrice` with `changeBalance` matcher, you will have to update them.

Before

    await expect(() =>
      sender.sendTransaction({
        to: receiver.address,
        gasPrice: 0,
        value: 200
      })
    ).to.changeBalance(sender, -200);

After

    const TX_GAS = 21000;
    const BASE_FEE_PER_GAS = 875000000
    const gasFees = BASE_FEE_PER_GAS * TX_GAS;
    await expect(() =>
      sender.sendTransaction({
        to: receiver.address,
        gasPrice: BASE_FEE_PER_GAS,
        value: 200
      })
    ).to.changeBalance(sender, -(gasFees + 200));

Currently there is no way to set `gasPrice` to `0` in `Ganache`. Instead of (deprecated) matcher `changeBalance`, new matcher `changeEtherBalance` can be used instead - which handles transaction fee calculation automatically.

##### Custom wallet mnemonic

Ganache has a build-in set of Wallets with positive Ether balance. In the new Ganache, you should not override the wallet config, otherwise you might end up with Wallets with no Ether balance.

    const provider = new MockProvider({
      ganacheOptions: {
        // Remove this, if exists:
        wallet: {
          mnemonic: 'horn horn horn horn horn horn horn horn horn horn horn horn'
        }
      }
    })

##### Chaining matchers

Now when testing events on a smart contract you can conveniently chain matchers. It can be especially useful when testing events.

    const tx = await contract.emitEventsOneAndTwo();
    await expect(tx)
          .to.emit(contract, 'One').withArgs(
            1,
            'One'
          )
          .to.emit(contract, 'Two').withArgs(
            2,
            'Two'
          )
          .to.not.emit(contract, 'Three');

`changeEtherBalance`, `changeEtherBalances`, `changeTokenbalance` and `changeTokenBalances` matchers also support chaining:

    await token.approve(complex.address, 100);
    const tx = await complex.doEverything(receiver.address, 100, {value: 200});
    await expect(tx)
      .to.changeTokenBalances(token, [sender, receiver], [-100, 100])
      .and.to.changeEtherBalances([sender, receiver], [-200, 200])
      .and.to.emit(complex, 'TransferredEther').withArgs(200)
      .and.to.emit(complex, 'TransferredTokens').withArgs(100);

Although you may find it more convenient to write multiple expects. The test below is equivalent to the one above:

    await token.approve(complex.address, 100);
    const tx = await complex.doEverything(receiver.address, 100, {value: 200});
    await expect(tx).to.changeTokenBalances(token, [sender, receiver], [-100, 100]);
    await expect(tx).to.changeEtherBalances([sender, receiver], [-200, 200]);
    await expect(tx).to.emit(complex, 'TransferredEther').withArgs(200);
    await expect(tx).to.emit(complex, 'TransferredTokens').withArgs(100);

Note that in both cases you can use `chai` negation `not`. In a case of a single expect everything after `not` is negated.

    await token.approve(complex.address, 100);
    const tx = await complex.doEverything(receiver.address, 100, {value: 200});
    await expect(expect(tx)
      .to.changeTokenBalances(token, [sender, receiver], [-100, 100])
      .and.to.emit(complex, 'TransferredTokens').withArgs(100)
      .and.not
      .to.emit(complex, 'UnusedEvent') // This is negated
      .and.to.changeEtherBalances([sender, receiver], [-100, 100])  // This is negated as well

##### Custom errors

Custom errors were introduced in Solidity v0.8.4. It is a convenient and gas-efficient way to explain to users why an operation failed. Custom errors are defined in a similar way as events:

    // SPDX-License-Identifier: GPL-3.0
    pragma solidity ^0.8.4;

    /// Insufficient balance for transfer. Needed `required` but only
    /// `available` available.
    /// @param available balance available.
    /// @param required requested amount to transfer.
    error InsufficientBalance(uint256 available, uint256 required);

    contract TestToken {
        mapping(address => uint) balance;
        function transfer(address to, uint256 amount) public {
            if (amount > balance[msg.sender])
                // Error call using named parameters. Equivalent to
                // revert InsufficientBalance(balance[msg.sender], amount);
                revert InsufficientBalance({
                    available: balance[msg.sender],
                    required: amount
                });
            balance[msg.sender] -= amount;
            balance[to] += amount;
        }
        // ...
    }

When using Waffle v4.0.0-alpha.* with Hardhat, you can test transactions being reverted with custom errors as well. Using the `.revertedWith` matcher you can capture the custom error’s name (`expect(tx).to.be.revertedWith('InsufficientBalance')`). If you want to access arguments of a custom error you should use `.withArgs` matcher after the `.revertedWith` matcher.

    await expect(token.transfer(receiver, 100))
        .to.be.revertedWith('InsufficientBalance')
        .withArgs(0, 100);

Read the Docs v: latest

Versions
  latest
  stable

Downloads
  pdf
  html
  epub

On Read the Docs
  Project Home
  Builds

Free document hosting provided by Read the Docs.