@nomicfoundation/hardhat-ethers-chai-matchers 3.0.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.md +52 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +16 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/internal/add-chai-matchers.d.ts +2 -0
- package/dist/src/internal/add-chai-matchers.d.ts.map +1 -0
- package/dist/src/internal/add-chai-matchers.js +41 -0
- package/dist/src/internal/add-chai-matchers.js.map +1 -0
- package/dist/src/internal/constants.d.ts +13 -0
- package/dist/src/internal/constants.d.ts.map +1 -0
- package/dist/src/internal/constants.js +13 -0
- package/dist/src/internal/constants.js.map +1 -0
- package/dist/src/internal/hook-handlers/network.d.ts +4 -0
- package/dist/src/internal/hook-handlers/network.d.ts.map +1 -0
- package/dist/src/internal/hook-handlers/network.js +15 -0
- package/dist/src/internal/hook-handlers/network.js.map +1 -0
- package/dist/src/internal/matchers/addressable.d.ts +2 -0
- package/dist/src/internal/matchers/addressable.d.ts.map +1 -0
- package/dist/src/internal/matchers/addressable.js +53 -0
- package/dist/src/internal/matchers/addressable.js.map +1 -0
- package/dist/src/internal/matchers/big-number.d.ts +2 -0
- package/dist/src/internal/matchers/big-number.d.ts.map +1 -0
- package/dist/src/internal/matchers/big-number.js +178 -0
- package/dist/src/internal/matchers/big-number.js.map +1 -0
- package/dist/src/internal/matchers/changeEtherBalance.d.ts +7 -0
- package/dist/src/internal/matchers/changeEtherBalance.d.ts.map +1 -0
- package/dist/src/internal/matchers/changeEtherBalance.js +77 -0
- package/dist/src/internal/matchers/changeEtherBalance.js.map +1 -0
- package/dist/src/internal/matchers/changeEtherBalances.d.ts +7 -0
- package/dist/src/internal/matchers/changeEtherBalances.d.ts.map +1 -0
- package/dist/src/internal/matchers/changeEtherBalances.js +96 -0
- package/dist/src/internal/matchers/changeEtherBalances.js.map +1 -0
- package/dist/src/internal/matchers/changeTokenBalance.d.ts +16 -0
- package/dist/src/internal/matchers/changeTokenBalance.d.ts.map +1 -0
- package/dist/src/internal/matchers/changeTokenBalance.js +148 -0
- package/dist/src/internal/matchers/changeTokenBalance.js.map +1 -0
- package/dist/src/internal/matchers/emit.d.ts +5 -0
- package/dist/src/internal/matchers/emit.d.ts.map +1 -0
- package/dist/src/internal/matchers/emit.js +122 -0
- package/dist/src/internal/matchers/emit.js.map +1 -0
- package/dist/src/internal/matchers/hexEqual.d.ts +2 -0
- package/dist/src/internal/matchers/hexEqual.d.ts.map +1 -0
- package/dist/src/internal/matchers/hexEqual.js +19 -0
- package/dist/src/internal/matchers/hexEqual.js.map +1 -0
- package/dist/src/internal/matchers/properAddress.d.ts +2 -0
- package/dist/src/internal/matchers/properAddress.d.ts.map +1 -0
- package/dist/src/internal/matchers/properAddress.js +7 -0
- package/dist/src/internal/matchers/properAddress.js.map +1 -0
- package/dist/src/internal/matchers/properHex.d.ts +2 -0
- package/dist/src/internal/matchers/properHex.d.ts.map +1 -0
- package/dist/src/internal/matchers/properHex.js +13 -0
- package/dist/src/internal/matchers/properHex.js.map +1 -0
- package/dist/src/internal/matchers/properPrivateKey.d.ts +2 -0
- package/dist/src/internal/matchers/properPrivateKey.d.ts.map +1 -0
- package/dist/src/internal/matchers/properPrivateKey.js +7 -0
- package/dist/src/internal/matchers/properPrivateKey.js.map +1 -0
- package/dist/src/internal/matchers/reverted/panic.d.ts +13 -0
- package/dist/src/internal/matchers/reverted/panic.d.ts.map +1 -0
- package/dist/src/internal/matchers/reverted/panic.js +36 -0
- package/dist/src/internal/matchers/reverted/panic.js.map +1 -0
- package/dist/src/internal/matchers/reverted/reverted.d.ts +2 -0
- package/dist/src/internal/matchers/reverted/reverted.d.ts.map +1 -0
- package/dist/src/internal/matchers/reverted/reverted.js +112 -0
- package/dist/src/internal/matchers/reverted/reverted.js.map +1 -0
- package/dist/src/internal/matchers/reverted/revertedWith.d.ts +2 -0
- package/dist/src/internal/matchers/reverted/revertedWith.d.ts.map +1 -0
- package/dist/src/internal/matchers/reverted/revertedWith.js +56 -0
- package/dist/src/internal/matchers/reverted/revertedWith.js.map +1 -0
- package/dist/src/internal/matchers/reverted/revertedWithCustomError.d.ts +5 -0
- package/dist/src/internal/matchers/reverted/revertedWithCustomError.d.ts.map +1 -0
- package/dist/src/internal/matchers/reverted/revertedWithCustomError.js +120 -0
- package/dist/src/internal/matchers/reverted/revertedWithCustomError.js.map +1 -0
- package/dist/src/internal/matchers/reverted/revertedWithPanic.d.ts +2 -0
- package/dist/src/internal/matchers/reverted/revertedWithPanic.d.ts.map +1 -0
- package/dist/src/internal/matchers/reverted/revertedWithPanic.js +74 -0
- package/dist/src/internal/matchers/reverted/revertedWithPanic.js.map +1 -0
- package/dist/src/internal/matchers/reverted/revertedWithoutReason.d.ts +2 -0
- package/dist/src/internal/matchers/reverted/revertedWithoutReason.d.ts.map +1 -0
- package/dist/src/internal/matchers/reverted/revertedWithoutReason.js +41 -0
- package/dist/src/internal/matchers/reverted/revertedWithoutReason.js.map +1 -0
- package/dist/src/internal/matchers/reverted/utils.d.ts +39 -0
- package/dist/src/internal/matchers/reverted/utils.d.ts.map +1 -0
- package/dist/src/internal/matchers/reverted/utils.js +108 -0
- package/dist/src/internal/matchers/reverted/utils.js.map +1 -0
- package/dist/src/internal/matchers/withArgs.d.ts +16 -0
- package/dist/src/internal/matchers/withArgs.d.ts.map +1 -0
- package/dist/src/internal/matchers/withArgs.js +88 -0
- package/dist/src/internal/matchers/withArgs.js.map +1 -0
- package/dist/src/internal/utils/account.d.ts +3 -0
- package/dist/src/internal/utils/account.d.ts.map +1 -0
- package/dist/src/internal/utils/account.js +15 -0
- package/dist/src/internal/utils/account.js.map +1 -0
- package/dist/src/internal/utils/asserts.d.ts +5 -0
- package/dist/src/internal/utils/asserts.d.ts.map +1 -0
- package/dist/src/internal/utils/asserts.js +73 -0
- package/dist/src/internal/utils/asserts.js.map +1 -0
- package/dist/src/internal/utils/balance.d.ts +8 -0
- package/dist/src/internal/utils/balance.d.ts.map +1 -0
- package/dist/src/internal/utils/balance.js +19 -0
- package/dist/src/internal/utils/balance.js.map +1 -0
- package/dist/src/internal/utils/bigint.d.ts +2 -0
- package/dist/src/internal/utils/bigint.d.ts.map +1 -0
- package/dist/src/internal/utils/bigint.js +4 -0
- package/dist/src/internal/utils/bigint.js.map +1 -0
- package/dist/src/internal/utils/build-assert.d.ts +19 -0
- package/dist/src/internal/utils/build-assert.d.ts.map +1 -0
- package/dist/src/internal/utils/build-assert.js +39 -0
- package/dist/src/internal/utils/build-assert.js.map +1 -0
- package/dist/src/internal/utils/ordinal.d.ts +8 -0
- package/dist/src/internal/utils/ordinal.d.ts.map +1 -0
- package/dist/src/internal/utils/ordinal.js +21 -0
- package/dist/src/internal/utils/ordinal.js.map +1 -0
- package/dist/src/internal/utils/prevent-chaining.d.ts +2 -0
- package/dist/src/internal/utils/prevent-chaining.d.ts.map +1 -0
- package/dist/src/internal/utils/prevent-chaining.js +17 -0
- package/dist/src/internal/utils/prevent-chaining.js.map +1 -0
- package/dist/src/internal/utils/ssfi.d.ts +4 -0
- package/dist/src/internal/utils/ssfi.d.ts.map +1 -0
- package/dist/src/internal/utils/ssfi.js +2 -0
- package/dist/src/internal/utils/ssfi.js.map +1 -0
- package/dist/src/internal/utils/typed.d.ts +2 -0
- package/dist/src/internal/utils/typed.d.ts.map +1 -0
- package/dist/src/internal/utils/typed.js +10 -0
- package/dist/src/internal/utils/typed.js.map +1 -0
- package/dist/src/panic.d.ts +2 -0
- package/dist/src/panic.d.ts.map +1 -0
- package/dist/src/panic.js +2 -0
- package/dist/src/panic.js.map +1 -0
- package/dist/src/type-extensions.d.ts +45 -0
- package/dist/src/type-extensions.d.ts.map +1 -0
- package/dist/src/type-extensions.js +2 -0
- package/dist/src/type-extensions.js.map +1 -0
- package/dist/src/withArgs.d.ts +2 -0
- package/dist/src/withArgs.d.ts.map +1 -0
- package/dist/src/withArgs.js +2 -0
- package/dist/src/withArgs.js.map +1 -0
- package/package.json +85 -0
- package/src/index.ts +21 -0
- package/src/internal/add-chai-matchers.ts +46 -0
- package/src/internal/constants.ts +13 -0
- package/src/internal/hook-handlers/network.ts +24 -0
- package/src/internal/matchers/addressable.ts +86 -0
- package/src/internal/matchers/big-number.ts +279 -0
- package/src/internal/matchers/changeEtherBalance.ts +138 -0
- package/src/internal/matchers/changeEtherBalances.ts +188 -0
- package/src/internal/matchers/changeTokenBalance.ts +295 -0
- package/src/internal/matchers/emit.ts +232 -0
- package/src/internal/matchers/hexEqual.ts +29 -0
- package/src/internal/matchers/properAddress.ts +12 -0
- package/src/internal/matchers/properHex.ts +29 -0
- package/src/internal/matchers/properPrivateKey.ts +12 -0
- package/src/internal/matchers/reverted/panic.ts +36 -0
- package/src/internal/matchers/reverted/reverted.ts +165 -0
- package/src/internal/matchers/reverted/revertedWith.ts +100 -0
- package/src/internal/matchers/reverted/revertedWithCustomError.ts +243 -0
- package/src/internal/matchers/reverted/revertedWithPanic.ts +118 -0
- package/src/internal/matchers/reverted/revertedWithoutReason.ts +73 -0
- package/src/internal/matchers/reverted/utils.ts +147 -0
- package/src/internal/matchers/withArgs.ts +139 -0
- package/src/internal/utils/account.ts +24 -0
- package/src/internal/utils/asserts.ts +156 -0
- package/src/internal/utils/balance.ts +39 -0
- package/src/internal/utils/bigint.ts +3 -0
- package/src/internal/utils/build-assert.ts +54 -0
- package/src/internal/utils/ordinal.ts +24 -0
- package/src/internal/utils/prevent-chaining.ts +33 -0
- package/src/internal/utils/ssfi.ts +6 -0
- package/src/internal/utils/typed.ts +9 -0
- package/src/panic.ts +1 -0
- package/src/type-extensions.ts +82 -0
- package/src/withArgs.ts +1 -0
@@ -0,0 +1,139 @@
|
|
1
|
+
import { HardhatError } from "@nomicfoundation/hardhat-errors";
|
2
|
+
import { toBigInt } from "@nomicfoundation/hardhat-utils/bigint";
|
3
|
+
import { AssertionError } from "chai";
|
4
|
+
import { isAddressable } from "ethers/address";
|
5
|
+
|
6
|
+
import { ASSERTION_ABORTED } from "../constants.js";
|
7
|
+
import { isBigInt } from "../utils/bigint.js";
|
8
|
+
|
9
|
+
import { emitWithArgs, EMIT_CALLED } from "./emit.js";
|
10
|
+
import {
|
11
|
+
revertedWithCustomErrorWithArgs,
|
12
|
+
REVERTED_WITH_CUSTOM_ERROR_CALLED,
|
13
|
+
} from "./reverted/revertedWithCustomError.js";
|
14
|
+
|
15
|
+
/**
|
16
|
+
* A predicate for use with .withArgs(...), to induce chai to accept any value
|
17
|
+
* as a positive match with the argument.
|
18
|
+
*
|
19
|
+
* Example: expect(contract.emitInt()).to.emit(contract, "Int").withArgs(anyValue)
|
20
|
+
*/
|
21
|
+
export function anyValue(): boolean {
|
22
|
+
return true;
|
23
|
+
}
|
24
|
+
|
25
|
+
/**
|
26
|
+
* A predicate for use with .withArgs(...), to induce chai to accept any
|
27
|
+
* unsigned integer as a positive match with the argument.
|
28
|
+
*
|
29
|
+
* Example: expect(contract.emitUint()).to.emit(contract, "Uint").withArgs(anyUint)
|
30
|
+
*/
|
31
|
+
export function anyUint(i: any): boolean {
|
32
|
+
if (typeof i === "number") {
|
33
|
+
if (i < 0) {
|
34
|
+
// eslint-disable-next-line no-restricted-syntax -- keep the original chai error structure
|
35
|
+
throw new AssertionError(
|
36
|
+
`anyUint expected its argument to be an unsigned integer, but it was negative, with value ${i}`,
|
37
|
+
);
|
38
|
+
}
|
39
|
+
|
40
|
+
return true;
|
41
|
+
} else if (isBigInt(i)) {
|
42
|
+
const bigInt = toBigInt(i);
|
43
|
+
|
44
|
+
if (bigInt < 0) {
|
45
|
+
// eslint-disable-next-line no-restricted-syntax -- keep the original chai error structure
|
46
|
+
throw new AssertionError(
|
47
|
+
`anyUint expected its argument to be an unsigned integer, but it was negative, with value ${bigInt}`,
|
48
|
+
);
|
49
|
+
}
|
50
|
+
return true;
|
51
|
+
}
|
52
|
+
|
53
|
+
// eslint-disable-next-line no-restricted-syntax -- keep the original chai error structure
|
54
|
+
throw new AssertionError(
|
55
|
+
`anyUint expected its argument to be an integer, but its type was '${typeof i}'`,
|
56
|
+
);
|
57
|
+
}
|
58
|
+
|
59
|
+
export function supportWithArgs(
|
60
|
+
Assertion: Chai.AssertionStatic,
|
61
|
+
chaiUtils: Chai.ChaiUtils,
|
62
|
+
): void {
|
63
|
+
Assertion.addMethod("withArgs", function (this: any, ...expectedArgs: any[]) {
|
64
|
+
const { emitCalled } = validateInput.call(this, chaiUtils);
|
65
|
+
|
66
|
+
// Resolve arguments to their canonical form:
|
67
|
+
// - Addressable → address
|
68
|
+
const resolveArgument = (arg: any) =>
|
69
|
+
isAddressable(arg) ? arg.getAddress() : arg;
|
70
|
+
|
71
|
+
const onSuccess = (resolvedExpectedArgs: any[]) => {
|
72
|
+
if (emitCalled) {
|
73
|
+
return emitWithArgs(
|
74
|
+
this,
|
75
|
+
Assertion,
|
76
|
+
chaiUtils,
|
77
|
+
resolvedExpectedArgs,
|
78
|
+
onSuccess,
|
79
|
+
);
|
80
|
+
} else {
|
81
|
+
return revertedWithCustomErrorWithArgs(
|
82
|
+
this,
|
83
|
+
Assertion,
|
84
|
+
chaiUtils,
|
85
|
+
resolvedExpectedArgs,
|
86
|
+
onSuccess,
|
87
|
+
);
|
88
|
+
}
|
89
|
+
};
|
90
|
+
|
91
|
+
const promise = (this.then === undefined ? Promise.resolve() : this)
|
92
|
+
.then(() => Promise.all(expectedArgs.map(resolveArgument)))
|
93
|
+
.then(onSuccess);
|
94
|
+
|
95
|
+
this.then = promise.then.bind(promise);
|
96
|
+
this.catch = promise.catch.bind(promise);
|
97
|
+
return this;
|
98
|
+
});
|
99
|
+
}
|
100
|
+
|
101
|
+
function validateInput(
|
102
|
+
this: any,
|
103
|
+
chaiUtils: Chai.ChaiUtils,
|
104
|
+
): { emitCalled: boolean } {
|
105
|
+
try {
|
106
|
+
if (Boolean(this.__flags.negate)) {
|
107
|
+
throw new HardhatError(
|
108
|
+
HardhatError.ERRORS.CHAI_MATCHERS.WITH_ARGS_CANNOT_BE_COMBINED_WITH_NOT,
|
109
|
+
);
|
110
|
+
}
|
111
|
+
|
112
|
+
const emitCalled = chaiUtils.flag(this, EMIT_CALLED) === true;
|
113
|
+
|
114
|
+
const revertedWithCustomErrorCalled =
|
115
|
+
chaiUtils.flag(this, REVERTED_WITH_CUSTOM_ERROR_CALLED) === true;
|
116
|
+
|
117
|
+
if (!emitCalled && !revertedWithCustomErrorCalled) {
|
118
|
+
throw new HardhatError(
|
119
|
+
HardhatError.ERRORS.CHAI_MATCHERS.WITH_ARGS_WRONG_COMBINATION,
|
120
|
+
);
|
121
|
+
}
|
122
|
+
|
123
|
+
if (emitCalled && revertedWithCustomErrorCalled) {
|
124
|
+
throw new HardhatError(
|
125
|
+
HardhatError.ERRORS.CHAI_MATCHERS.WITH_ARGS_COMBINED_WITH_INCOMPATIBLE_ASSERTIONS,
|
126
|
+
);
|
127
|
+
}
|
128
|
+
|
129
|
+
return { emitCalled };
|
130
|
+
} catch (e) {
|
131
|
+
// signal that validation failed to allow the matchers to finish early
|
132
|
+
chaiUtils.flag(this, ASSERTION_ABORTED, true);
|
133
|
+
|
134
|
+
// discard subject since it could potentially be a rejected promise
|
135
|
+
Promise.resolve(this._obj).catch(() => {});
|
136
|
+
|
137
|
+
throw e;
|
138
|
+
}
|
139
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import type { Addressable } from "ethers/address";
|
2
|
+
|
3
|
+
import { HardhatError } from "@nomicfoundation/hardhat-errors";
|
4
|
+
import { isAddress } from "@nomicfoundation/hardhat-utils/eth";
|
5
|
+
import { isAddressable } from "ethers/address";
|
6
|
+
|
7
|
+
export async function getAddressOf(
|
8
|
+
account: Addressable | string,
|
9
|
+
): Promise<string> {
|
10
|
+
if (isAddress(account)) {
|
11
|
+
return account;
|
12
|
+
}
|
13
|
+
|
14
|
+
if (isAddressable(account)) {
|
15
|
+
return account.getAddress();
|
16
|
+
}
|
17
|
+
|
18
|
+
throw new HardhatError(
|
19
|
+
HardhatError.ERRORS.CHAI_MATCHERS.EXPECTED_STRING_OR_ADDRESSABLE,
|
20
|
+
{
|
21
|
+
account,
|
22
|
+
},
|
23
|
+
);
|
24
|
+
}
|
@@ -0,0 +1,156 @@
|
|
1
|
+
import type { AssertWithSsfi, Ssfi } from "./ssfi.js";
|
2
|
+
|
3
|
+
import {
|
4
|
+
assertHardhatInvariant,
|
5
|
+
HardhatError,
|
6
|
+
} from "@nomicfoundation/hardhat-errors";
|
7
|
+
import { ensureError } from "@nomicfoundation/hardhat-utils/error";
|
8
|
+
import { keccak256 } from "ethers/crypto";
|
9
|
+
import { getBytes, hexlify, isHexString, toUtf8Bytes } from "ethers/utils";
|
10
|
+
|
11
|
+
import { ordinal } from "./ordinal.js";
|
12
|
+
|
13
|
+
export function assertIsNotNull<T>(
|
14
|
+
value: T,
|
15
|
+
valueName: string,
|
16
|
+
): asserts value is Exclude<T, null> {
|
17
|
+
assertHardhatInvariant(value !== null, `${valueName} should not be null`);
|
18
|
+
}
|
19
|
+
|
20
|
+
export function assertArgsArraysEqual(
|
21
|
+
Assertion: Chai.AssertionStatic,
|
22
|
+
expectedArgs: any[],
|
23
|
+
actualArgs: any[],
|
24
|
+
tag: string,
|
25
|
+
assertionType: "event" | "error",
|
26
|
+
assert: AssertWithSsfi,
|
27
|
+
ssfi: Ssfi,
|
28
|
+
): void {
|
29
|
+
try {
|
30
|
+
innerAssertArgsArraysEqual(
|
31
|
+
Assertion,
|
32
|
+
expectedArgs,
|
33
|
+
actualArgs,
|
34
|
+
assertionType,
|
35
|
+
assert,
|
36
|
+
ssfi,
|
37
|
+
);
|
38
|
+
} catch (err) {
|
39
|
+
ensureError(err);
|
40
|
+
err.message = `Error in ${tag}: ${err.message}`;
|
41
|
+
throw err;
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
function innerAssertArgsArraysEqual(
|
46
|
+
Assertion: Chai.AssertionStatic,
|
47
|
+
expectedArgs: any[],
|
48
|
+
actualArgs: any[],
|
49
|
+
assertionType: "event" | "error",
|
50
|
+
assert: AssertWithSsfi,
|
51
|
+
ssfi: Ssfi,
|
52
|
+
) {
|
53
|
+
assert(
|
54
|
+
actualArgs.length === expectedArgs.length,
|
55
|
+
`Expected arguments array to have length ${expectedArgs.length}, but it has ${actualArgs.length}`,
|
56
|
+
);
|
57
|
+
for (const [index, expectedArg] of expectedArgs.entries()) {
|
58
|
+
try {
|
59
|
+
innerAssertArgEqual(
|
60
|
+
Assertion,
|
61
|
+
expectedArg,
|
62
|
+
actualArgs[index],
|
63
|
+
assertionType,
|
64
|
+
assert,
|
65
|
+
ssfi,
|
66
|
+
);
|
67
|
+
} catch (err) {
|
68
|
+
ensureError(err);
|
69
|
+
err.message = `Error in the ${ordinal(index + 1)} argument assertion: ${
|
70
|
+
err.message
|
71
|
+
}`;
|
72
|
+
throw err;
|
73
|
+
}
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
function innerAssertArgEqual(
|
78
|
+
Assertion: Chai.AssertionStatic,
|
79
|
+
expectedArg: any,
|
80
|
+
actualArg: any,
|
81
|
+
assertionType: "event" | "error",
|
82
|
+
assert: AssertWithSsfi,
|
83
|
+
ssfi: Ssfi,
|
84
|
+
) {
|
85
|
+
if (typeof expectedArg === "function") {
|
86
|
+
try {
|
87
|
+
if (expectedArg(actualArg) === true) return;
|
88
|
+
} catch (e) {
|
89
|
+
ensureError(e);
|
90
|
+
|
91
|
+
assert(
|
92
|
+
false,
|
93
|
+
`The predicate threw when called: ${e.message}`,
|
94
|
+
// no need for a negated message, since we disallow mixing .not. with
|
95
|
+
// .withArgs
|
96
|
+
);
|
97
|
+
}
|
98
|
+
assert(
|
99
|
+
false,
|
100
|
+
`The predicate did not return true`,
|
101
|
+
// no need for a negated message, since we disallow mixing .not. with
|
102
|
+
// .withArgs
|
103
|
+
);
|
104
|
+
} else if (expectedArg instanceof Uint8Array) {
|
105
|
+
new Assertion(actualArg, undefined, ssfi, true).equal(hexlify(expectedArg));
|
106
|
+
} else if (
|
107
|
+
expectedArg?.length !== undefined &&
|
108
|
+
typeof expectedArg !== "string"
|
109
|
+
) {
|
110
|
+
innerAssertArgsArraysEqual(
|
111
|
+
Assertion,
|
112
|
+
expectedArg,
|
113
|
+
actualArg,
|
114
|
+
assertionType,
|
115
|
+
assert,
|
116
|
+
ssfi,
|
117
|
+
);
|
118
|
+
} else {
|
119
|
+
if (actualArg.hash !== undefined && actualArg._isIndexed === true) {
|
120
|
+
if (assertionType !== "event") {
|
121
|
+
throw new HardhatError(
|
122
|
+
HardhatError.ERRORS.CHAI_MATCHERS.INDEXED_EVENT_FORBIDDEN,
|
123
|
+
);
|
124
|
+
}
|
125
|
+
|
126
|
+
new Assertion(actualArg.hash, undefined, ssfi, true).to.not.equal(
|
127
|
+
expectedArg,
|
128
|
+
"The actual value was an indexed and hashed value of the event argument. The expected value provided to the assertion should be the actual event argument (the pre-image of the hash). You provided the hash itself. Please supply the actual event argument (the pre-image of the hash) instead.",
|
129
|
+
);
|
130
|
+
|
131
|
+
const expectedArgBytes = isHexString(expectedArg)
|
132
|
+
? getBytes(expectedArg)
|
133
|
+
: toUtf8Bytes(expectedArg);
|
134
|
+
|
135
|
+
const expectedHash = keccak256(expectedArgBytes);
|
136
|
+
|
137
|
+
new Assertion(actualArg.hash, undefined, ssfi, true).to.equal(
|
138
|
+
expectedHash,
|
139
|
+
`The actual value was an indexed and hashed value of the event argument. The expected value provided to the assertion was hashed to produce ${expectedHash}. The actual hash and the expected hash ${actualArg.hash} did not match`,
|
140
|
+
);
|
141
|
+
} else {
|
142
|
+
new Assertion(actualArg, undefined, ssfi, true).equal(expectedArg);
|
143
|
+
}
|
144
|
+
}
|
145
|
+
}
|
146
|
+
|
147
|
+
export function assertCanBeConvertedToBigint(
|
148
|
+
value: unknown,
|
149
|
+
): asserts value is string | number | bigint {
|
150
|
+
assertHardhatInvariant(
|
151
|
+
typeof value === "string" ||
|
152
|
+
typeof value === "number" ||
|
153
|
+
typeof value === "bigint",
|
154
|
+
"value should be of type string, number or bigint",
|
155
|
+
);
|
156
|
+
}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import type { Addressable } from "ethers";
|
2
|
+
import type { EthereumProvider } from "hardhat/types/providers";
|
3
|
+
|
4
|
+
import { toBigInt } from "@nomicfoundation/hardhat-utils/bigint";
|
5
|
+
import { numberToHexString } from "@nomicfoundation/hardhat-utils/hex";
|
6
|
+
|
7
|
+
import { getAddressOf } from "./account.js";
|
8
|
+
import { assertCanBeConvertedToBigint } from "./asserts.js";
|
9
|
+
|
10
|
+
export interface BalanceChangeOptions {
|
11
|
+
includeFee?: boolean;
|
12
|
+
}
|
13
|
+
|
14
|
+
export function getAddresses(
|
15
|
+
accounts: Array<Addressable | string>,
|
16
|
+
): Promise<string[]> {
|
17
|
+
return Promise.all(accounts.map((account) => getAddressOf(account)));
|
18
|
+
}
|
19
|
+
|
20
|
+
export async function getBalances(
|
21
|
+
provider: EthereumProvider,
|
22
|
+
accounts: Array<Addressable | string>,
|
23
|
+
blockNumber?: number,
|
24
|
+
): Promise<bigint[]> {
|
25
|
+
return Promise.all(
|
26
|
+
accounts.map(async (account) => {
|
27
|
+
const address = await getAddressOf(account);
|
28
|
+
|
29
|
+
const result = await provider.request({
|
30
|
+
method: "eth_getBalance",
|
31
|
+
params: [address, numberToHexString(blockNumber ?? 0)],
|
32
|
+
});
|
33
|
+
|
34
|
+
assertCanBeConvertedToBigint(result);
|
35
|
+
|
36
|
+
return toBigInt(result);
|
37
|
+
}),
|
38
|
+
);
|
39
|
+
}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import type { Ssfi } from "./ssfi.js";
|
2
|
+
|
3
|
+
import { HardhatError } from "@nomicfoundation/hardhat-errors";
|
4
|
+
import { AssertionError } from "chai";
|
5
|
+
|
6
|
+
/**
|
7
|
+
* This function is used by the matchers to obtain an `assert` function, which
|
8
|
+
* should be used instead of `this.assert`.
|
9
|
+
*
|
10
|
+
* The first parameter is the value of the `negated` flag. Keep in mind that
|
11
|
+
* this value should be captured at the beginning of the matcher's
|
12
|
+
* implementation, before any async code is executed. Otherwise things like
|
13
|
+
* `.to.emit().and.not.to.emit()` won't work, because by the time the async part
|
14
|
+
* of the first emit is executed, the `.not` (executed synchronously) has already
|
15
|
+
* modified the flag.
|
16
|
+
*
|
17
|
+
* The second parameter is what Chai calls the "start stack function indicator",
|
18
|
+
* a function that is used to build the stack trace. It's unclear to us what's
|
19
|
+
* the best way to use this value, so this needs some trial-and-error. Use the
|
20
|
+
* existing matchers for a reference of something that works well enough.
|
21
|
+
*/
|
22
|
+
export function buildAssert(negated: boolean, ssfi: Ssfi) {
|
23
|
+
return function (
|
24
|
+
condition: boolean,
|
25
|
+
messageFalse?: string | (() => string),
|
26
|
+
messageTrue?: string | (() => string),
|
27
|
+
): void {
|
28
|
+
if (!negated && !condition) {
|
29
|
+
if (messageFalse === undefined) {
|
30
|
+
throw new HardhatError(
|
31
|
+
HardhatError.ERRORS.CHAI_MATCHERS.ASSERTION_WITHOUT_ERROR_MESSAGE,
|
32
|
+
);
|
33
|
+
}
|
34
|
+
|
35
|
+
const message =
|
36
|
+
typeof messageFalse === "function" ? messageFalse() : messageFalse;
|
37
|
+
// eslint-disable-next-line no-restricted-syntax -- keep the original chai error structure
|
38
|
+
throw new AssertionError(message, undefined, ssfi);
|
39
|
+
}
|
40
|
+
|
41
|
+
if (negated && condition) {
|
42
|
+
if (messageTrue === undefined) {
|
43
|
+
throw new HardhatError(
|
44
|
+
HardhatError.ERRORS.CHAI_MATCHERS.ASSERTION_WITHOUT_ERROR_MESSAGE,
|
45
|
+
);
|
46
|
+
}
|
47
|
+
|
48
|
+
const message =
|
49
|
+
typeof messageTrue === "function" ? messageTrue() : messageTrue;
|
50
|
+
// eslint-disable-next-line no-restricted-syntax -- keep the original chai error structure
|
51
|
+
throw new AssertionError(message, undefined, ssfi);
|
52
|
+
}
|
53
|
+
};
|
54
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
/**
|
2
|
+
* Appends the appropriate ordinal suffix ("st", "nd", "rd", "th") to a given number.
|
3
|
+
*
|
4
|
+
* This function correctly handles special cases such as numbers ending in 11, 12, and 13,
|
5
|
+
* which always use the "th" suffix, and ensures the correct suffix for all other numbers.
|
6
|
+
*/
|
7
|
+
export function ordinal(n: number): string {
|
8
|
+
const s = ["th", "st", "nd", "rd"];
|
9
|
+
const v = n % 100;
|
10
|
+
|
11
|
+
const suffixIndex = (v - 20) % 10;
|
12
|
+
let suffix = s[suffixIndex];
|
13
|
+
|
14
|
+
if (suffixIndex >= 1 && suffixIndex <= 3) {
|
15
|
+
return n + suffix;
|
16
|
+
}
|
17
|
+
|
18
|
+
suffix = s[v];
|
19
|
+
if (v >= 1 && v <= 3) {
|
20
|
+
return n + suffix;
|
21
|
+
}
|
22
|
+
|
23
|
+
return n + s[0];
|
24
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import { HardhatError } from "@nomicfoundation/hardhat-errors";
|
2
|
+
|
3
|
+
import { PREVIOUS_MATCHER_NAME } from "../constants.js";
|
4
|
+
|
5
|
+
export function preventAsyncMatcherChaining(
|
6
|
+
context: object,
|
7
|
+
matcherName: string,
|
8
|
+
chaiUtils: Chai.ChaiUtils,
|
9
|
+
allowSelfChaining: boolean = false,
|
10
|
+
): void {
|
11
|
+
const previousMatcherName: string | undefined = chaiUtils.flag(
|
12
|
+
context,
|
13
|
+
PREVIOUS_MATCHER_NAME,
|
14
|
+
);
|
15
|
+
|
16
|
+
if (previousMatcherName === undefined) {
|
17
|
+
chaiUtils.flag(context, PREVIOUS_MATCHER_NAME, matcherName);
|
18
|
+
|
19
|
+
return;
|
20
|
+
}
|
21
|
+
|
22
|
+
if (previousMatcherName === matcherName && allowSelfChaining) {
|
23
|
+
return;
|
24
|
+
}
|
25
|
+
|
26
|
+
throw new HardhatError(
|
27
|
+
HardhatError.ERRORS.CHAI_MATCHERS.MATCHER_CANNOT_BE_CHAINED_AFTER,
|
28
|
+
{
|
29
|
+
matcher: matcherName,
|
30
|
+
previousMatcher: previousMatcherName,
|
31
|
+
},
|
32
|
+
);
|
33
|
+
}
|
package/src/panic.ts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export { PANIC_CODES } from "./internal/matchers/reverted/panic.js";
|
@@ -0,0 +1,82 @@
|
|
1
|
+
import type { HardhatEthers } from "@nomicfoundation/hardhat-ethers/types";
|
2
|
+
import type { EthereumProvider } from "hardhat/types/providers";
|
3
|
+
|
4
|
+
// We use declare global instead of declare module "chai", because that's what
|
5
|
+
// @types/chai does.
|
6
|
+
declare global {
|
7
|
+
/* eslint-disable-next-line @typescript-eslint/no-namespace -- We have to use
|
8
|
+
a namespace because @types/chai uses it. */
|
9
|
+
namespace Chai {
|
10
|
+
interface Assertion
|
11
|
+
extends LanguageChains,
|
12
|
+
NumericComparison,
|
13
|
+
TypeComparison {
|
14
|
+
emit(contract: any, eventName: string): EmitAssertion;
|
15
|
+
reverted(ethers: HardhatEthers): AsyncAssertion;
|
16
|
+
revertedWith(reason: string | RegExp): AsyncAssertion;
|
17
|
+
revertedWithoutReason(ethers: HardhatEthers): AsyncAssertion;
|
18
|
+
revertedWithPanic(code?: any): AsyncAssertion;
|
19
|
+
revertedWithCustomError(
|
20
|
+
contract: { interface: any },
|
21
|
+
customErrorName: string,
|
22
|
+
): CustomErrorAssertion;
|
23
|
+
hexEqual(other: string): void;
|
24
|
+
properPrivateKey: void;
|
25
|
+
properAddress: void;
|
26
|
+
properHex(length: number): void;
|
27
|
+
changeEtherBalance(
|
28
|
+
provider: EthereumProvider,
|
29
|
+
account: any,
|
30
|
+
balance: any,
|
31
|
+
options?: any,
|
32
|
+
): AsyncAssertion;
|
33
|
+
changeEtherBalances(
|
34
|
+
provider: EthereumProvider,
|
35
|
+
accounts: any[],
|
36
|
+
balances: any[] | ((changes: bigint[]) => boolean),
|
37
|
+
options?: any,
|
38
|
+
): AsyncAssertion;
|
39
|
+
changeTokenBalance(
|
40
|
+
provider: EthereumProvider,
|
41
|
+
token: any,
|
42
|
+
account: any,
|
43
|
+
balance: any,
|
44
|
+
): AsyncAssertion;
|
45
|
+
changeTokenBalances(
|
46
|
+
provider: EthereumProvider,
|
47
|
+
token: any,
|
48
|
+
account: any[],
|
49
|
+
balance: any[] | ((changes: bigint[]) => boolean),
|
50
|
+
): AsyncAssertion;
|
51
|
+
}
|
52
|
+
|
53
|
+
interface NumericComparison {
|
54
|
+
within(start: any, finish: any, message?: string): Assertion;
|
55
|
+
}
|
56
|
+
|
57
|
+
interface NumberComparer {
|
58
|
+
// eslint-disable-next-line -- the interface must follow the original definition pattern
|
59
|
+
(value: any, message?: string): Assertion;
|
60
|
+
}
|
61
|
+
|
62
|
+
interface CloseTo {
|
63
|
+
// eslint-disable-next-line -- the interface must follow the original definition pattern
|
64
|
+
(expected: any, delta: any, message?: string): Assertion;
|
65
|
+
}
|
66
|
+
|
67
|
+
interface Length extends Assertion {
|
68
|
+
// eslint-disable-next-line -- the interface must follow the original definition pattern
|
69
|
+
(length: any, message?: string): Assertion;
|
70
|
+
}
|
71
|
+
|
72
|
+
interface AsyncAssertion extends Assertion, Promise<void> {}
|
73
|
+
|
74
|
+
interface EmitAssertion extends AsyncAssertion {
|
75
|
+
withArgs(...args: any[]): AsyncAssertion;
|
76
|
+
}
|
77
|
+
|
78
|
+
interface CustomErrorAssertion extends AsyncAssertion {
|
79
|
+
withArgs(...args: any[]): AsyncAssertion;
|
80
|
+
}
|
81
|
+
}
|
82
|
+
}
|
package/src/withArgs.ts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export { anyUint, anyValue } from "./internal/matchers/withArgs.js";
|