Skip to content

add readme for viem matchers #6734

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/sixty-beans-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@nomicfoundation/hardhat-viem-matchers": patch
"@nomicfoundation/hardhat-toolbox-viem": patch
"hardhat": patch
---

Add a new Hardhat assertion plugin for `viem` ([#6574](https://github.com/NomicFoundation/hardhat/pull/6574))
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions v-next/example-project/contracts/FailingContract.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
pragma solidity *;

contract FailingContract {
error CustomError();
error CustomErrorWithUintAndString(uint, string);

function fail() public pure {
innerRevert();
}

function innerRevert() internal pure {
revert("Revert Message");
}

function failByRevertWithCustomError() external pure {
revert CustomError();
}

function failByRevertWithCustomErrorWithUintAndString(
uint n,
string memory s
) external pure {
revert CustomErrorWithUintAndString(n, s);
}
}
6 changes: 6 additions & 0 deletions v-next/example-project/contracts/Rocket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ contract Rocket {
string public name;
string public status;

event LaunchWithoutArgs();
event LaunchWithTwoStringArgs(string u, string v);

constructor(string memory _name) {
name = _name;
status = "ignition";
}

function launch() public {
status = "lift-off";

emit LaunchWithoutArgs();
emit LaunchWithTwoStringArgs(name, status);
}
}
2 changes: 2 additions & 0 deletions v-next/example-project/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import HardhatNodeTestRunner from "@nomicfoundation/hardhat-node-test-runner";
import HardhatMochaTestRunner from "@nomicfoundation/hardhat-mocha";
import HardhatKeystore from "@nomicfoundation/hardhat-keystore";
import HardhatViem from "@nomicfoundation/hardhat-viem";
import HardhatViemMatchers from "@nomicfoundation/hardhat-viem-matchers";
import hardhatNetworkHelpersPlugin from "@nomicfoundation/hardhat-network-helpers";
import hardhatEthersPlugin from "@nomicfoundation/hardhat-ethers";
import hardhatChaiMatchersPlugin from "@nomicfoundation/hardhat-ethers-chai-matchers";
Expand Down Expand Up @@ -156,6 +157,7 @@ const config: HardhatUserConfig = {
hardhatNetworkHelpersPlugin,
HardhatNodeTestRunner,
HardhatViem,
HardhatViemMatchers,
hardhatChaiMatchersPlugin,
hardhatTypechain,
hardhatIgnitionViem,
Expand Down
1 change: 1 addition & 0 deletions v-next/example-project/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@nomicfoundation/hardhat-node-test-runner": "workspace:^3.0.0-next.11",
"@nomicfoundation/hardhat-typechain": "workspace:^3.0.0-next.11",
"@nomicfoundation/hardhat-viem": "workspace:^3.0.0-next.11",
"@nomicfoundation/hardhat-viem-matchers": "workspace:^3.0.0-next.11",
"@openzeppelin/contracts": "5.1.0",
"@types/chai": "^4.2.0",
"@types/mocha": ">=10.0.10",
Expand Down
82 changes: 82 additions & 0 deletions v-next/example-project/test/node/example-test-of-assertions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { describe, it, before } from "node:test";
import hre from "hardhat";
import { ContractReturnType } from "@nomicfoundation/hardhat-viem/types";

const { viem } = await hre.network.connect();

describe("Example EDR based test", () => {
describe("revert", () => {
let failingContract: ContractReturnType<"FailingContract">;

before(async () => {
failingContract = await viem.deployContract("FailingContract");
});

it("should support checking that a transaction reverts", async () => {
await viem.assertions.revert(failingContract.read.fail());
});

it("should support checking that a transaction reverts with a specific message", async () => {
await viem.assertions.revertWith(
failingContract.read.fail(),
"Revert Message",
);
});

it("should support checking that a transaction reverts with a custom error and specific arguments", async () => {
await viem.assertions.revertWithCustomErrorWithArgs(
failingContract.read.failByRevertWithCustomErrorWithUintAndString([
10n,
"example",
]),
failingContract,
"CustomErrorWithUintAndString",
[10n, "example"],
);
});
});

describe("Events", () => {
it("should support detecting an emitted event", async () => {
const rocketContract = await viem.deployContract("Rocket", ["Apollo"]);

await viem.assertions.emit(
rocketContract.write.launch(),
rocketContract,
"LaunchWithoutArgs",
);
});

it("should support detecting an emitted events arguments", async () => {
const rocketContract = await viem.deployContract("Rocket", ["Apollo"]);

await viem.assertions.emitWithArgs(
rocketContract.write.launch(),
rocketContract,
"LaunchWithTwoStringArgs",
["Apollo", "lift-off"],
);
});
});

it("should support detecting a change of balance", async () => {
const [bobWalletClient, aliceWalletClient] = await viem.getWalletClients();

await viem.assertions.balancesHaveChanged(
bobWalletClient.sendTransaction({
to: aliceWalletClient.account.address,
value: 3333333333333333n,
}),
[
{
address: aliceWalletClient.account.address,
amount: 3333333333333333n,
},
{
address: bobWalletClient.account.address,
amount: -3333333333333333n,
},
],
);
});
});
3 changes: 3 additions & 0 deletions v-next/example-project/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
{
"path": "../hardhat-viem"
},
{
"path": "../hardhat-viem-matchers"
},
{
"path": "../hardhat-ethers-chai-matchers"
},
Expand Down
2 changes: 2 additions & 0 deletions v-next/hardhat-toolbox-viem/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@nomicfoundation/hardhat-node-test-runner": "workspace:^3.0.0-next.11",
"@nomicfoundation/hardhat-test-utils": "workspace:^",
"@nomicfoundation/hardhat-viem": "workspace:^3.0.0-next.11",
"@nomicfoundation/hardhat-viem-matchers": "workspace:^3.0.0-next.11",
"@nomicfoundation/ignition-core": "workspace:^3.0.0-next.11",
"@types/node": "^20.14.9",
"c8": "^9.1.0",
Expand All @@ -71,6 +72,7 @@
"@nomicfoundation/hardhat-network-helpers": "workspace:^3.0.0-next.11",
"@nomicfoundation/hardhat-node-test-runner": "workspace:^3.0.0-next.11",
"@nomicfoundation/hardhat-viem": "workspace:^3.0.0-next.11",
"@nomicfoundation/hardhat-viem-matchers": "workspace:^3.0.0-next.11",
"@nomicfoundation/ignition-core": "workspace:^3.0.0-next.11",
"hardhat": "workspace:^3.0.0-next.11",
"viem": "^2.30.0"
Expand Down
6 changes: 6 additions & 0 deletions v-next/hardhat-toolbox-viem/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ const hardhatToolboxViemPlugin: HardhatPlugin = {
);
return hardhatViemPlugin;
},
async () => {
const { default: hardhatViemMatchers } = await import(
"@nomicfoundation/hardhat-viem-matchers"
);
return hardhatViemMatchers;
},
],
npmPackage: "@nomicfoundation/hardhat-toolbox-viem",
};
Expand Down
1 change: 1 addition & 0 deletions v-next/hardhat-toolbox-viem/src/type-extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export type * from "@nomicfoundation/hardhat-keystore";
export type * from "@nomicfoundation/hardhat-network-helpers";
export type * from "@nomicfoundation/hardhat-node-test-runner";
export type * from "@nomicfoundation/hardhat-viem";
export type * from "@nomicfoundation/hardhat-viem-matchers";
3 changes: 3 additions & 0 deletions v-next/hardhat-toolbox-viem/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
},
{
"path": "../hardhat-viem"
},
{
"path": "../hardhat-viem-matchers"
}
]
}
162 changes: 161 additions & 1 deletion v-next/hardhat-viem-matchers/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,161 @@
# TODO
# Hardhat Viem Matchers plugin

This plugin adds an Ethereum-specific matchers assertion library that integrate with [viem](https://viem.sh/), making your smart contract tests easy to write and read.

## Installation

To install this plugin, run the following command:

```bash
npm install --save-dev @nomicfoundation/hardhat-viem-matchers@next
```

and add the following statements to your `hardhat.config.ts` file:

```typescript
// ...
import viemMatchersPlugin from "@nomicfoundation/hardhat-viem-matchers";

// ...

export default {
// ...
plugins: [
// ...
viemMatchersPlugin,
],

// ...
};
```

## Usage

You don't need to do anything else to use this plugin. Whenever you run your tests with Hardhat, it will automatically add the matchers to the `viem` object.

Here is an example of using the `balancesHaveChanged` matcher:

```ts
const { viem } = await hre.network.connect();

const [bobWalletClient, aliceWalletClient] = await viem.getWalletClients();

await viem.assertions.balancesHaveChanged(
bobWalletClient.sendTransaction({
to: aliceWalletClient.account.address,
value: 3333333333333333n,
}),
[
{
address: aliceWalletClient.account.address,
amount: 3333333333333333n,
},
],
);
```

## Reference

### Reverted transactions

Several matchers are included to assert that a transaction reverted, and the reason of the revert.

#### `.revert`

Assert that a transaction reverted for any reason, without checking the cause of the revert:

```ts
await viem.assertions.revert(token.write.transfer([address, 0n]));
```

#### `.revertWith`

Assert that a transaction reverted with a specific reason string:

```ts
await viem.assertions.revertWith(
token.write.transfer([address, 0n]),
"transfer value must be positive",
);
```

#### `.revertWithCustomError`

Assert that a transaction reverted with a specific custom error:

```ts
await viem.assertions.revertWithCustomError(
token.write.transfer([address, 0n]),
token,
"InvalidTransferValue",
);
```

The second argument must be the contract that defines the error. The contract is used to determine the full signature of the expected error. The matcher does not check whether the error was emitted by the contract.

#### `.revertWithCustomErrorWithArgs`

Assert that a transaction reverted with a custom error and specific arguments:

```ts
await viem.assertions.revertWithCustomErrorWithArgs(
token.write.transfer([address, 0n]),
token,
"InvalidTransferValue",
[0n],
);
```

### Events

#### `.emit`

Assert that a transaction emits a specific event:

```ts
await viem.assertions.emit(
rocketContract.write.launch(),
rocketContract,
"LaunchEvent",
);
```

#### `.emitWithArgs`

Assert that a transaction emits an event with specific arguments:

```ts
await viem.assertions.emitWithArgs(
rocketContract.write.launch(),
rocketContract,
"LaunchEventWithArgs",
["Apollo", "lift-off"],
);
```

### Balance change

These matchers can be used to assert how a given transaction affects the ether balance of a specific address.

#### `.balancesHaveChanged`

Assert that a transaction changes the balance of specific addresses:

```ts
await viem.assertions.balancesHaveChanged(
bobWalletClient.sendTransaction({
to: aliceWalletClient.account.address,
value: 3333333333333333n,
}),
[
{
address: aliceWalletClient.account.address,
amount: 3333333333333333n,
},
{
address: bobWalletClient.account.address,
amount: -3333333333333333n,
},
],
);
```
Loading