Waffle Framework
In the realm of blockchain development, smart contracts play a vital role in ensuring the security and functionality of decentralized applications (dApps). As these contracts become increasingly complex, the need for reliable testing frameworks becomes crucial. In this blog post, we will dive into the Waffle framework, a powerful tool that simplifies and enhances smart contract testing. We will explore its features, installation process, and shed light on its significance in the development lifecycle.
What is the Waffle Framework?
The Waffle framework is an open-source testing library specifically designed for smart contracts written in Solidity, the most widely used programming language for Ethereum. It provides developers with a set of utilities and APIs that simplify the process of writing comprehensive unit tests for their smart contracts. Waffle offers a declarative and intuitive approach to testing, allowing developers to focus on the logic and behavior of their contracts without getting bogged down in the intricacies of the testing process.
Why is the Waffle Framework Required?
Enhanced Testing Capabilities:
The Waffle framework offers a rich set of testing tools, including advanced assertion functions and powerful mocking capabilities. These features allow developers to write more robust tests, ensuring that their smart contracts perform as expected in different scenarios.
Improved Test Readability:
Waffle introduces a declarative syntax that enhances test readability and maintainability. By using intuitive functions and methods, developers can write more expressive tests, making it easier for others to understand the intended behavior.
Seamless Integration with Existing Tools:
Waffle seamlessly integrates with other widely used development tools and frameworks, such as Truffle and Hardhat. This compatibility simplifies the testing process by leveraging familiar workflows and tools, reducing the learning curve for developers.
How to Install Waffle:
To get started with Waffle, follow these steps:
Step 1: Install Node.js and npm (Node Package Manager) if you haven't already.
Step 2: Create a new project directory for your smart contracts.
Step 3: Open a terminal and navigate to the project directory.
Step 4: Run the following command to initialize a new npm project:
npm init -y
Step 5: Install the Waffle framework and its dependencies using npm:
npm install --save-dev ethereum-waffle chai ethers
Step 6: You are now ready to start writing tests using the Waffle framework!
How Waffle Works
Waffle operates by extending the capabilities of popular testing libraries, such as Mocha and Chai. It provides additional functions and syntax specifically tailored for smart contract testing. Developers can write tests that interact with their contracts and make assertions about their behavior.
Waffle allows you to deploy and interact with contracts in a local Ethereum development environment, providing a sandboxed environment for testing without incurring the cost of actual deployments on the mainnet or testnets. This local testing environment significantly speeds up the testing process.
Let's walk through an example of deploying and testing a basic smart contract using the Waffle framework:
We'll create a simple "Counter" contract that allows us to increment and retrieve a counter value.
Step 1: Set Up the Project
Create a new directory for your project and navigate to it in the terminal.
Step 2: Install Dependencies
Initialize a new npm project and install the necessary dependencies by running the following commands:
npm init -y
npm install --save-dev ethereum-waffle chai ethers
Step 3: Write the Smart Contract
Create a new file named Counter.sol and add the following code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Counter {
uint256 public counter;
function increment() public {
counter++;
}
function getCounter() public view returns (uint256) {
return counter;
}
}
Step 4: Write the Test
Create a new file named counter.test.js and add the following code:
const { ethers, waffle } = require('hardhat');
const { expect } = require('chai');
describe('Counter', function () {
let counter;
let deployer;
beforeEach(async function () {
[deployer] = await ethers.getSigners();
const Counter = await
ethers.getContractFactory('Counter');
counter = await Counter.deploy();
await counter.deployed();
});
it('should initialize the counter to zero', async function () {
expect(await counter.getCounter()).to.equal(0);
});
it('should increment the counter', async function () {
await counter.increment();
expect(await counter.getCounter()).to.equal(1);
});
});
Step 5: Run the Tests
In the terminal, run the following command to execute the tests:
npx hardhat test
You should see the test results displayed in the terminal, indicating whether they passed or failed. If everything is working correctly, both tests should pass.
Explanation:
In the test file, we import the necessary libraries (ethers, waffle, and chai) and define a test suite using the describe function. Inside the suite, we declare variables for the counter instance and the deployer account.
In the beforeEach hook, we use ethers to deploy an instance of the Counter contract using the Counter contract factory. We assign the deployed contract to the counter variable for use in the individual tests.
The first test, 'should initialize the counter to zero', verifies that the initial counter value is set to zero. We use the expect function from chai to assert that the value returned by getCounter() is equal to 0.
The second test, 'should increment the counter', checks if the counter increments correctly when we call the increment() function. After calling increment(), we again use expect to assert that the value returned by getCounter() is equal to 1.
By running npx hardhat test, we execute the tests using the Hardhat framework and the Waffle plugin. If the tests pass, you'll see a success message indicating that the contract behaves as expected.
You can watch this
by Bartek Rutkowski to gain more information on smart contract testing using waffle framework and this
by Bartłomiej Rutkowski on Testing smart contracts with Waffle.
Waffle mocking:
In Waffle you can mock contract calls automatically. It's like an integrated gnosis/mock-contract. Instead of deployContract you just use deployMockContract when deploying in your tests. Then you have the ability to change the return values of contract calls.
One drawback is that this works only when calling a contract within a contract. So given our Greeter example, to use the feature we need to add a GreetManager contract. Let's also add a string parameter to the greet function. As you can see, we can mock every call using mockGreeter.mock.greet.returns("mockedReturn 1") or mock depending on the passed arguments using mockGreeter.mock.greet .withArgs("argument") .returns("mockedReturn 2")
//SPDX-License-Identifier: MIT pragma solidity ^0.6.8; import "./Greeter.sol"; contract GreetManager { Greeter greeter; constructor(Greeter greeter_) public { greeter = greeter_; } function useGreeter(string memory custom) public view returns (string memory) { return greeter.greet(custom); } } const { expect } = require("chai"); const { deployContract, deployMockContract, MockProvider, } = require("ethereum-waffle"); const Greeter = require("../artifacts/Greeter.json"); const GreetManager = require("../artifacts/GreetManager.json"); const wallet = new MockProvider().getWallets()[0];
describe("Greeter", () => { it("Should return what is set by mock", async () => { const mockGreeter = await deployMockContract(wallet, Greeter.abi, [ "Hello world!", ]); const greetManager = await deployContract(wallet, GreetManager, [ mockGreeter.address,
]); await mockGreeter.mock.greet.returns("Hello new world!"); await mockGreeter.mock.greet .withArgs("Hello old world!") .returns("Hello new (not old) world!"); expect(await greetManager.useGreeter("custom")).to.equal( "Hello new world!" ); expect(await greetManager.useGreeter("Hello old world!")).to.equal( "Hello new (not old) world!" ); }); });
ENS (Ethereum Name Service):
ENS is nowadays quite often supported. You can register domains and subdomains in it. Many projects like MetaMask support this, so you can use simple names instead of addresses. If you are using it in your contracts, you can easily deploy the complete ENS smart contracts system with Waffle.
const provider = new MockProvider();
dir="ltr">await provider.setupENS();
This is all you need for the setup. Now you can create domains and subdomains:
await ens.createTopLevelDomain('com');
await ens.createSubDomain('soliditydeveloper.com');
await ens.createSubDomain('vitalik.buterin.eth', {recursive: true}); // all in one
And you can set addresses via:
// name to address
await ens.setAddress('soliditydeveloper.com', '0x007...03');
// address to name
await ens.setAddressWithReverse('soliditydeveloper.com', '0x007...03');
FAQ Regarding Waffle:
Q1: Can I use Waffle with frameworks other than Truffle and Hardhat?
A1: Yes, Waffle is compatible with various development frameworks. While Truffle and Hardhat are commonly used, you can integrate Waffle into your preferred development setup.
Q2: Does Waffle support contract mocking?
A2: Yes, Waffle provides powerful mocking capabilities, allowing you to simulate different contract behaviors and test edge cases.
Q3: Are there any limitations to using Waffle?
A3: Waffle primarily focuses on smart contract testing and may not offer the same level of features for other aspects of blockchain development, such as deployment or frontend interaction.
Q4: Can I use Waffle with other blockchain platforms besides Ethereum?
A4: Waffle is primarily designed for Ethereum smart contract testing, but its underlying libraries and concepts may be applicable to other blockchain platforms with Solidity support.
Q5: Waffle or Truffle?
A5: I think both are viable options these days. Both have unique features. In particular I like the Waffle fixtures as they can speed up test runs significantly. If you feel like that's something you don't really need, Truffle might be the better option just due to more active developments, more tooling and support.
More Q&A cover here.