Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
2
Help us understand the problem. What are the problem?

posted at

updated at

Waffle vs Truffle

結論

忙しい人のために結論から言うと、Waffleです。

2021/10/26追記
悪いことは言わないから、hardhat(+ethers)使っときなさい

理由

1.テストフレームワークが自由
 Truffleはmochaが組み込まれていて、mocha一択ですが、Waffleはavaだろうがjestだろうが自由に選択できます。

2.実行が早い
 今時のテストフレームワークは各々のテストをパラレル実行できるオプションがあります。
 Waffleはもちろんそれを利用できます。が、Truffleはmocha(もパラレル実行機能はあるのですが、、、)を利用しているにもかかわらず、それができません

3.安定している
 Truffleを使うとよくプロセスが残ったままになり、そのまま再実行するとエラーになります。psコマンドで適宜プロセスを切ればいいのですが、そもそもプロセスはちゃんと切れてくれるのが理想ではあります。

4.記述もシンプル
 おまけ程度のメリットになります。後述しますが、Truffleに比べWaffleの方がシンプルに記述できると思います。(個人の主観)

注意

最新のWaffleはSolidity0.5系に対応していないので、Solidity自体を0.6系以上で書くか、もしくは古いWaffle(version2.1.0)を使うかしなければなりません。そしてWaffleはバージョンが上がると結構破壊的な修正が入るため、その辺りを考慮して導入してください。

比較

TruffleとWaffleの書き方を比較していきます。

ウォレットの生成

Truffleの場合はcontract(最初にこれを書かないと行けない)のところに記述すれば、テストで利用することができます。
Waffleの場合はテスト内で利用できるウォレットを取得することができます。
Truffleはstring型でそのままウォレットのアドレスが渡されるのですが、Waffleの場合はWolletオブジェクトなので、その辺りの注意が必要です。

[Truffle]
contract('test', ([wallet1, wallet2]) => {
    describe('test title', () => {
        it('アドレスを表示する', async () => {
            console.log(wallet1)
            console.log(wallet2)
        })
    })
})


[Waffle]
import {MockProvider} from "ethereum-waffle"
describe("test", () => {
    it("test title.", async () => {
        const [wallet1, wallet12] = new MockProvider().getWallets()
        console.log(wallet1.address)
        console.log(wallet12.address)
    })
})

コントラクトの生成

Truffleはartifactsと言うオブジェクトが最初から利用可能になっていて、それを利用してコントラクトをデプロイします。
WaffleはdeployContractと言う関数とbuildした結果出力されるjsonファイルをimportして利用します。

[Truffle]
contract('test', ([wallet1, wallet2]) => {
    describe('test title', () => {
        it('コントラクトをデプロイする', async () => {
            const contract = await artifacts.require('ContractName').new(arg1, arg2, {from: wallet1})
        })
    })
})

[Waffle]
import {deployContract, MockProvider} from "ethereum-waffle"
import * as TestContarct from "../build/TestContarct.json"

describe("test", () => {
    it("test title.", async () => {
        const [wallet1, wallet12] = new MockProvider().getWallets()
        const contract = await deployContract(wallet1, TestContarct, [arg1, arg2])
    })
})

実行wallet変更

実行Walletを指定する場合、Truffleは実行関数の引数に指定します。
Waffleはconnectメソッドを利用します。

[Truffle]
contract('test', ([wallet1, wallet2]) => {
    describe('test title', () => {
        it('実行ウォレットの変更', async () => {
            const contract = await artifacts.require('ContractName').new(arg1, arg2, {from: wallet1})
            await contract.func() //何も指定しない場合、wallet1(contractで一番左のアドレス)で実行される
            await contract.func({from: wallet2}) //指定すれば指定したウォレットによる実行となる
        })
    })
})

[Waffle]
import {deployContract, MockProvider} from "ethereum-waffle"
import * as TestContarct from "../build/TestContarct.json"

describe("test", () => {
    it("test title.", async () => {
        const [wallet1, wallet12] = new MockProvider().getWallets()
        const contract = await deployContract(wallet1, TestContarct, [arg1, arg2])
        contractOtherWallet = contract.connect(wallet12)
        await contract.func() //wallet1の実行
        await contractOtherWallet.func() //wallet2の実行
    })
})

アサーション

Truffleは最初から組み込まれているアサーション機能を利用します。
Waffleはchaiを利用します。

[Truffle]
contract('test', ([wallet1, wallet2]) => {
    describe('test title', () => {
        it('アドレスを比較する', async () => {
            expect(wallet1).to.be.equal('0x0293674538abcd.....')
        })
    })
})

[Waffle]
import {expect, use} from "chai"
import {MockProvider, solidity} from "ethereum-waffle"

use(solidity);

describe("test", () => {
    it("test title.", async () => {
        const [wallet1, wallet12] = new MockProvider().getWallets()
        expect(wallet1.address).to.be.equal('0x0293674538abcd.....')
    })
})

アサーション(エラーチェック)

Truffleはtry catchなどでエラーを補足します。
Waffleは「use(solidity)」をすることにより、便利なエラーチェック関数を利用することができます。

[Truffle]
contract('test', ([wallet1, wallet2]) => {
    describe('test title', () => {
        it('エラーが発生することを確認する', async () => {
            const contract = await artifacts.require('contract name').new(arg1, arg2, {from: wallet1})
            const res = await contract.func().catch((err: Error) => err)
            expect(res).to.be.an.instanceof(Error)
            expect((res as Error).message).to.include('error message')
        })
    })
})

[Waffle]
import {expect, use} from "chai";
import {MockProvider, deployContract, solidity} from "ethereum-waffle"
import * as TestContarct from "../build/TestContarct.json"

use(solidity);

describe("test", () => {
    it("test title.", async () => {
        const [wallet1] = new MockProvider().getWallets()
        const contract = await deployContract(wallet1, TestContarct, [arg1, arg2])
        await expect(contract.func()).to.be.revertedWith('error message')
    })
})

イベントの取得

Truffleはtruffle-assertionsというライブラリを使うといい感じにできます。
Waffleは関数実行時についでにチェックする方法と、フィルターを使った、後からチェックする方法があります。

[Truffle]
import {assert, use} from "chai"
const truffleAssert = require('truffle-assertions');

contract('test', ([wallet1, wallet2]) => {
    describe('test title', () => {
        it('イベントをチェックする', async () => {
            const contract = await artifacts.require('contract name').new(arg1, arg2, {from: wallet1})
            const transactionInfo = await contract.func()
            truffleAssert.eventEmitted(transactionInfo, 'EventName', (ev) => {
                return ev.user === 'hogehoge' && !ev.age.eq(5)
            })
            // イベントが発生しないことをチェックする方法
            truffleAssert.eventNotEmitted(transactionInfo, 'EventName')
        })
    })
})

[Waffle]
import {expect, use} from "chai"
import {MockProvider, deployContract, solidity} from "ethereum-waffle"
import * as TestContarct from "../build/TestContarct.json"

use(solidity)

describe("test", () => {
    it("test title.", async () => {
        const [wallet1] = new MockProvider().getWallets()
        const contract = await deployContract(wallet1, TestContarct, [arg1, arg2])
        // イベントのチェック
        await expect(expect(contract.func()).to.be.revertedWith('error message'))
        .to.emit(contract, 'EventName').withArgs(
            'hogehoge',
            10
        )
        // イベントのデータ自体も取得できる
        const filter = contract.filters.EventName()
        const events = await contract.queryFilter(filter)
        console.log(events)
    })
})

関数実行

Truffleの場合はweb3をimportして実行します。web3自体はnewせずともテスト用のganechaに勝手に接続されて、使える状態になります。
Waffleの場合はproviderを利用するだけです。simpleでいいです。

[Truffle]
import Web3 from 'web3'

contract('test', ([wallet1, wallet2]) => {
    describe('test title', () => {
        it('ブロックを進める', async () => {
            await new Promise(function (resolve) {
                web3.currentProvider.send(
                    {
                        jsonrpc: '2.0',
                        method: 'evm_mine',
                        params: [],
                        id: 0,
                    },
                    resolve
                )
            })
        })
    })
})

[Waffle]
import {MockProvider} from "ethereum-waffle"

describe("test", () => {
    it("test title.", async () => {
        const provider = new MockProvider()
        await provider.send('evm_mine', [])
    })
})


Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
2
Help us understand the problem. What are the problem?