Skip to content

Commit 9f89015

Browse files
authored
Merge pull request #7064 from NomicFoundation/add-predicates-to-viem-assertions
Support predicates on viem-assertions
2 parents de47767 + de38508 commit 9f89015

File tree

11 files changed

+440
-16
lines changed

11 files changed

+440
-16
lines changed

.changeset/lovely-dodos-tell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@nomicfoundation/hardhat-viem-assertions": patch
3+
---
4+
5+
Support predicates on viem-assertions ([#7038](https://github.com/NomicFoundation/hardhat/issues/7038))

v-next/hardhat-viem-assertions/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"types": "dist/src/index.d.ts",
1515
"exports": {
1616
".": "./dist/src/index.js",
17-
"./types": "./dist/src/types.js"
17+
"./types": "./dist/src/types.js",
18+
"./predicates": "./dist/src/predicates.js"
1819
},
1920
"keywords": [
2021
"ethereum",

v-next/hardhat-viem-assertions/src/internal/assertions/emit/emit-with-args.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import type {
1414
import assert from "node:assert/strict";
1515

1616
import { assertHardhatInvariant } from "@nomicfoundation/hardhat-errors";
17-
import { deepEqual } from "@nomicfoundation/hardhat-utils/lang";
17+
18+
import { stringifyArgs } from "../../helpers.js";
19+
import { isArgumentMatch } from "../../predicates.js";
1820

1921
import { handleEmit } from "./core.js";
2022

@@ -51,7 +53,7 @@ export async function emitWithArgs<
5153
const parsedLogs = await handleEmit(viem, contractFn, contract, eventName);
5254

5355
for (const { args: logArgs } of parsedLogs) {
54-
let emittedArgs: unknown[] = [];
56+
let emittedArgs: any[] = [];
5557

5658
if (logArgs === undefined) {
5759
if (expectedArgs.length === 0) {
@@ -80,17 +82,36 @@ export async function emitWithArgs<
8082
}
8183
}
8284

83-
if ((await deepEqual(emittedArgs, expectedArgs)) === true) {
85+
if (await isArgumentMatch(emittedArgs, expectedArgs)) {
8486
return;
8587
}
8688

8789
if (parsedLogs.length === 1) {
8890
// Provide additional error details only if a single event was emitted
89-
assert.deepEqual(
90-
emittedArgs,
91-
expectedArgs,
92-
"The event arguments do not match the expected ones.",
93-
);
91+
92+
if (expectedArgs.some((arg) => typeof arg === "function")) {
93+
// If there are predicate matchers, we can't use the built-in deepEqual with diff
94+
const displayExpectedArgs = expectedArgs.map((expectedArg) => {
95+
if (typeof expectedArg === "function") {
96+
const hasName =
97+
expectedArg.name !== undefined && expectedArg.name !== "";
98+
return `<${hasName ? expectedArg.name : "predicate"}>`;
99+
} else {
100+
return expectedArg;
101+
}
102+
});
103+
104+
assert.fail(
105+
`The event arguments do not match the expected ones:\nExpected: ${stringifyArgs(displayExpectedArgs)}\nEmitted: ${stringifyArgs(emittedArgs)}`,
106+
);
107+
} else {
108+
// Otherwise, we can use it
109+
assert.deepEqual(
110+
emittedArgs,
111+
expectedArgs,
112+
"The event arguments do not match the expected ones.",
113+
);
114+
}
94115
}
95116
}
96117

v-next/hardhat-viem-assertions/src/internal/assertions/revert/revert-with-custom-error-with-args.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import type { ReadContractReturnType, WriteContractReturnType } from "viem";
66

77
import assert from "node:assert/strict";
88

9+
import { stringifyArgs } from "../../helpers.js";
10+
import { isArgumentMatch } from "../../predicates.js";
11+
912
import { handleRevertWithCustomError } from "./handle-revert-with-custom-error.js";
1013

1114
export async function revertWithCustomErrorWithArgs<
@@ -14,17 +17,34 @@ export async function revertWithCustomErrorWithArgs<
1417
contractFn: Promise<ReadContractReturnType | WriteContractReturnType>,
1518
contract: ContractReturnType<ContractName>,
1619
customErrorName: string,
17-
args: any[],
20+
expectedArgs: any[],
1821
): Promise<void> {
1922
const errorArgs = await handleRevertWithCustomError(
2023
contractFn,
2124
contract,
2225
customErrorName,
2326
);
2427

25-
assert.deepEqual(
26-
errorArgs,
27-
args,
28-
`The function was expected to revert with arguments "${args.join(", ")}", but it reverted with arguments "${errorArgs.join(", ")}".`,
29-
);
28+
if (await isArgumentMatch(errorArgs, expectedArgs)) {
29+
return;
30+
}
31+
32+
// No match, then show error
33+
if (expectedArgs.some((arg) => typeof arg === "function")) {
34+
// If there are predicate matchers, we can't use the built-in deepEqual with diff
35+
const displayExpectedArgs = expectedArgs.map((expectedArg) =>
36+
typeof expectedArg === "function" ? "<predicate>" : expectedArg,
37+
);
38+
39+
assert.fail(
40+
`The error arguments do not match the expected ones:\nExpected: ${stringifyArgs(displayExpectedArgs)}\nRaised: ${stringifyArgs(errorArgs)}`,
41+
);
42+
} else {
43+
// Otherwise, we can use it
44+
assert.deepEqual(
45+
errorArgs,
46+
expectedArgs,
47+
`The function was expected to revert with arguments "${expectedArgs.join(", ")}", but it reverted with arguments "${errorArgs.join(", ")}".`,
48+
);
49+
}
3050
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* This is a JSON.stringify function with a custom replacer that stringifies bigints as strings.
3+
*/
4+
export function stringifyArgs(obj: any): string {
5+
return JSON.stringify(obj, (key, value) =>
6+
typeof value === "bigint" ? value.toString() : value,
7+
);
8+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import assert from "node:assert/strict";
2+
3+
import { ensureError } from "@nomicfoundation/hardhat-utils/error";
4+
import { deepEqual } from "@nomicfoundation/hardhat-utils/lang";
5+
6+
export function anyValue() {
7+
return true;
8+
}
9+
10+
export async function isArgumentMatch(
11+
actualArgs: any[],
12+
expectedArgs: any[],
13+
): Promise<boolean> {
14+
assert.ok(
15+
actualArgs.length === expectedArgs.length,
16+
`${actualArgs.length} arguments emitted, but ${expectedArgs.length} expected`,
17+
);
18+
19+
for (let index = 0; index < actualArgs.length; index++) {
20+
const emittedArg = actualArgs[index];
21+
const expectedArg = expectedArgs[index];
22+
23+
if (typeof expectedArg === "function") {
24+
try {
25+
if (expectedArg(emittedArg) !== true) {
26+
return false;
27+
}
28+
} catch (e) {
29+
ensureError(e);
30+
assert.fail(
31+
`The predicate of index ${index} threw when called: ${e.message}`,
32+
);
33+
}
34+
} else {
35+
if (!(await deepEqual(emittedArg, expectedArg))) {
36+
return false;
37+
}
38+
}
39+
}
40+
41+
return true;
42+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { anyValue } from "./internal/predicates.js";

v-next/hardhat-viem-assertions/test/fixture-projects/hardhat-project/contracts/Events.sol

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.24;
33

4+
struct TestStruct {
5+
uint a;
6+
uint b;
7+
}
8+
49
contract Events {
510
event WithoutArgs();
611
event WithIntArg(int i);
@@ -11,6 +16,9 @@ contract Events {
1116
event SameEventDifferentArgs(uint u, uint v);
1217
event SameEventDifferentArgs(uint u, string s);
1318
event SameEventDifferentArgs(uint u, uint v, string s);
19+
event WithString(string s);
20+
event WithArray(uint[] a);
21+
event WithStruct(TestStruct a);
1422

1523
constructor() {}
1624

@@ -47,4 +55,16 @@ contract Events {
4755
function emitSameEventDifferentArgs3(uint u, uint v, string memory s) public {
4856
emit SameEventDifferentArgs(u, v, s);
4957
}
58+
59+
function emitString(string memory s) public {
60+
emit WithString(s);
61+
}
62+
63+
function emitArray(uint[] memory a) public {
64+
emit WithArray(a);
65+
}
66+
67+
function emitStruct(TestStruct memory a) public {
68+
emit WithStruct(a);
69+
}
5070
}

v-next/hardhat-viem-assertions/test/fixture-projects/hardhat-project/contracts/Revert.sol

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.24;
33

4+
struct TestStruct {
5+
uint a;
6+
uint b;
7+
}
8+
49
contract Revert {
510
//
611
// Custom errors
@@ -9,6 +14,8 @@ contract Revert {
914
error CustomErrorWithInt(int);
1015
error CustomErrorWithUintAndString(uint, string);
1116
error CustomErrorWithUintAndStringNamedParam(uint u, uint v, string s);
17+
error CustomErrorWithArray(uint[]);
18+
error CustomErrorWithStruct(TestStruct);
1219

1320
function alwaysRevert() external pure {
1421
revert("Intentional revert for testing purposes");
@@ -38,6 +45,14 @@ contract Revert {
3845
) external pure {
3946
revert CustomErrorWithUintAndStringNamedParam(u, v, s);
4047
}
48+
49+
function revertWithCustomErrorWithArray(uint[] memory a) external pure {
50+
revert CustomErrorWithArray(a);
51+
}
52+
53+
function revertWithCustomErrorWithStruct(TestStruct memory a) external pure {
54+
revert CustomErrorWithStruct(a);
55+
}
4156
}
4257

4358
contract RevertWithNestedError {

0 commit comments

Comments
 (0)