@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,232 @@
|
|
1
|
+
import type { AssertWithSsfi, Ssfi } from "../utils/ssfi.js";
|
2
|
+
import type { EventFragment } from "ethers/abi";
|
3
|
+
import type { Contract } from "ethers/contract";
|
4
|
+
import type { Provider, TransactionReceipt } from "ethers/providers";
|
5
|
+
import type { Transaction } from "ethers/transaction";
|
6
|
+
|
7
|
+
import util from "node:util";
|
8
|
+
|
9
|
+
import { HardhatError } from "@nomicfoundation/hardhat-errors";
|
10
|
+
import { AssertionError } from "chai";
|
11
|
+
|
12
|
+
import { ASSERTION_ABORTED, EMIT_MATCHER } from "../constants.js";
|
13
|
+
import { assertArgsArraysEqual, assertIsNotNull } from "../utils/asserts.js";
|
14
|
+
import { buildAssert } from "../utils/build-assert.js";
|
15
|
+
import { preventAsyncMatcherChaining } from "../utils/prevent-chaining.js";
|
16
|
+
|
17
|
+
export const EMIT_CALLED = "emitAssertionCalled";
|
18
|
+
|
19
|
+
async function waitForPendingTransaction(
|
20
|
+
tx: Promise<Transaction> | Transaction | string,
|
21
|
+
provider: Provider,
|
22
|
+
) {
|
23
|
+
let hash: string | null;
|
24
|
+
if (tx instanceof Promise) {
|
25
|
+
({ hash } = await tx);
|
26
|
+
} else if (typeof tx === "string") {
|
27
|
+
hash = tx;
|
28
|
+
} else {
|
29
|
+
({ hash } = tx);
|
30
|
+
}
|
31
|
+
|
32
|
+
if (hash === null) {
|
33
|
+
throw new HardhatError(
|
34
|
+
HardhatError.ERRORS.CHAI_MATCHERS.INVALID_TRANSACTION,
|
35
|
+
{ transaction: JSON.stringify(tx) },
|
36
|
+
);
|
37
|
+
}
|
38
|
+
|
39
|
+
return provider.getTransactionReceipt(hash);
|
40
|
+
}
|
41
|
+
|
42
|
+
export function supportEmit(
|
43
|
+
Assertion: Chai.AssertionStatic,
|
44
|
+
chaiUtils: Chai.ChaiUtils,
|
45
|
+
): void {
|
46
|
+
Assertion.addMethod(
|
47
|
+
EMIT_MATCHER,
|
48
|
+
function (
|
49
|
+
this: any,
|
50
|
+
contract: Contract,
|
51
|
+
eventName: string,
|
52
|
+
...args: any[]
|
53
|
+
) {
|
54
|
+
// capture negated flag before async code executes; see buildAssert's jsdoc
|
55
|
+
const negated = this.__flags.negate;
|
56
|
+
const tx = this._obj;
|
57
|
+
|
58
|
+
preventAsyncMatcherChaining(this, EMIT_MATCHER, chaiUtils, true);
|
59
|
+
|
60
|
+
const promise = this.then === undefined ? Promise.resolve() : this;
|
61
|
+
|
62
|
+
const onSuccess = (receipt: TransactionReceipt) => {
|
63
|
+
// abort if the assertion chain was aborted, for example because
|
64
|
+
// a `.not` was combined with a `.withArgs`
|
65
|
+
if (chaiUtils.flag(this, ASSERTION_ABORTED) === true) {
|
66
|
+
return;
|
67
|
+
}
|
68
|
+
|
69
|
+
const assert = buildAssert(negated, onSuccess);
|
70
|
+
|
71
|
+
let eventFragment: EventFragment | null = null;
|
72
|
+
try {
|
73
|
+
eventFragment = contract.interface.getEvent(eventName, []);
|
74
|
+
} catch (e) {
|
75
|
+
if (e instanceof TypeError) {
|
76
|
+
const errorMessage = e.message.split(" (argument=")[0];
|
77
|
+
// eslint-disable-next-line no-restricted-syntax -- keep the original chai error structure
|
78
|
+
throw new AssertionError(errorMessage);
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
if (eventFragment === null) {
|
83
|
+
// eslint-disable-next-line no-restricted-syntax -- keep the original chai error structure
|
84
|
+
throw new AssertionError(
|
85
|
+
`Event "${eventName}" doesn't exist in the contract`,
|
86
|
+
);
|
87
|
+
}
|
88
|
+
|
89
|
+
const topic = eventFragment.topicHash;
|
90
|
+
const contractAddress = contract.target;
|
91
|
+
if (typeof contractAddress !== "string") {
|
92
|
+
throw new HardhatError(
|
93
|
+
HardhatError.ERRORS.CHAI_MATCHERS.CONTRACT_TARGET_MUST_BE_A_STRING,
|
94
|
+
);
|
95
|
+
}
|
96
|
+
|
97
|
+
if (args.length > 0) {
|
98
|
+
throw new HardhatError(
|
99
|
+
HardhatError.ERRORS.CHAI_MATCHERS.EMIT_EXPECTS_TWO_ARGUMENTS,
|
100
|
+
);
|
101
|
+
}
|
102
|
+
|
103
|
+
this.logs = receipt.logs
|
104
|
+
.filter((log) => log.topics.includes(topic))
|
105
|
+
.filter(
|
106
|
+
(log) =>
|
107
|
+
log.address.toLowerCase() === contractAddress.toLowerCase(),
|
108
|
+
);
|
109
|
+
|
110
|
+
assert(
|
111
|
+
this.logs.length > 0,
|
112
|
+
`Expected event "${eventName}" to be emitted, but it wasn't`,
|
113
|
+
`Expected event "${eventName}" NOT to be emitted, but it was`,
|
114
|
+
);
|
115
|
+
chaiUtils.flag(this, "eventName", eventName);
|
116
|
+
chaiUtils.flag(this, "contract", contract);
|
117
|
+
};
|
118
|
+
|
119
|
+
const derivedPromise = promise.then(() => {
|
120
|
+
// abort if the assertion chain was aborted, for example because
|
121
|
+
// a `.not` was combined with a `.withArgs`
|
122
|
+
if (chaiUtils.flag(this, ASSERTION_ABORTED) === true) {
|
123
|
+
return;
|
124
|
+
}
|
125
|
+
|
126
|
+
if (contract.runner === null || contract.runner.provider === null) {
|
127
|
+
throw new HardhatError(
|
128
|
+
HardhatError.ERRORS.CHAI_MATCHERS.CONTRACT_RUNNER_PROVIDER_NOT_NULL,
|
129
|
+
);
|
130
|
+
}
|
131
|
+
|
132
|
+
return waitForPendingTransaction(tx, contract.runner.provider).then(
|
133
|
+
(receipt) => {
|
134
|
+
assertIsNotNull(receipt, "receipt");
|
135
|
+
return onSuccess(receipt);
|
136
|
+
},
|
137
|
+
);
|
138
|
+
});
|
139
|
+
|
140
|
+
chaiUtils.flag(this, EMIT_CALLED, true);
|
141
|
+
|
142
|
+
this.then = derivedPromise.then.bind(derivedPromise);
|
143
|
+
this.catch = derivedPromise.catch.bind(derivedPromise);
|
144
|
+
this.promise = derivedPromise;
|
145
|
+
return this;
|
146
|
+
},
|
147
|
+
);
|
148
|
+
}
|
149
|
+
|
150
|
+
export async function emitWithArgs(
|
151
|
+
context: any,
|
152
|
+
Assertion: Chai.AssertionStatic,
|
153
|
+
chaiUtils: Chai.ChaiUtils,
|
154
|
+
expectedArgs: any[],
|
155
|
+
ssfi: Ssfi,
|
156
|
+
): Promise<void> {
|
157
|
+
const negated = false; // .withArgs cannot be negated
|
158
|
+
const assert = buildAssert(negated, ssfi);
|
159
|
+
|
160
|
+
tryAssertArgsArraysEqual(
|
161
|
+
context,
|
162
|
+
Assertion,
|
163
|
+
chaiUtils,
|
164
|
+
expectedArgs,
|
165
|
+
context.logs,
|
166
|
+
assert,
|
167
|
+
ssfi,
|
168
|
+
);
|
169
|
+
}
|
170
|
+
|
171
|
+
const tryAssertArgsArraysEqual = (
|
172
|
+
context: any,
|
173
|
+
Assertion: Chai.AssertionStatic,
|
174
|
+
chaiUtils: Chai.ChaiUtils,
|
175
|
+
expectedArgs: any[],
|
176
|
+
logs: any[],
|
177
|
+
assert: AssertWithSsfi,
|
178
|
+
ssfi: Ssfi,
|
179
|
+
) => {
|
180
|
+
const eventName = chaiUtils.flag(context, "eventName");
|
181
|
+
|
182
|
+
if (logs.length === 1) {
|
183
|
+
const parsedLog = chaiUtils
|
184
|
+
.flag(context, "contract")
|
185
|
+
.interface.parseLog(logs[0]);
|
186
|
+
assertIsNotNull(parsedLog, "parsedLog");
|
187
|
+
|
188
|
+
return assertArgsArraysEqual(
|
189
|
+
Assertion,
|
190
|
+
expectedArgs,
|
191
|
+
parsedLog.args,
|
192
|
+
`"${eventName}" event`,
|
193
|
+
"event",
|
194
|
+
assert,
|
195
|
+
ssfi,
|
196
|
+
);
|
197
|
+
}
|
198
|
+
|
199
|
+
for (const index in logs) {
|
200
|
+
if (index === undefined) {
|
201
|
+
break;
|
202
|
+
} else {
|
203
|
+
try {
|
204
|
+
const parsedLog = chaiUtils
|
205
|
+
.flag(context, "contract")
|
206
|
+
.interface.parseLog(logs[index]);
|
207
|
+
assertIsNotNull(parsedLog, "parsedLog");
|
208
|
+
|
209
|
+
assertArgsArraysEqual(
|
210
|
+
Assertion,
|
211
|
+
expectedArgs,
|
212
|
+
parsedLog.args,
|
213
|
+
`"${eventName}" event`,
|
214
|
+
"event",
|
215
|
+
assert,
|
216
|
+
ssfi,
|
217
|
+
);
|
218
|
+
|
219
|
+
return;
|
220
|
+
} catch {}
|
221
|
+
}
|
222
|
+
}
|
223
|
+
|
224
|
+
assert(
|
225
|
+
false,
|
226
|
+
`The specified arguments (${util.inspect(
|
227
|
+
expectedArgs,
|
228
|
+
)}) were not included in any of the ${
|
229
|
+
context.logs.length
|
230
|
+
} emitted "${eventName}" events`,
|
231
|
+
);
|
232
|
+
};
|
@@ -0,0 +1,29 @@
|
|
1
|
+
export function supportHexEqual(Assertion: Chai.AssertionStatic): void {
|
2
|
+
Assertion.addMethod("hexEqual", function (this: any, other: string) {
|
3
|
+
const subject = this._obj;
|
4
|
+
const isNegated = this.__flags.negate === true;
|
5
|
+
|
6
|
+
// check that both values are proper hex strings
|
7
|
+
const isHex = (a: string) => /^0x[0-9a-fA-F]*$/.test(a);
|
8
|
+
for (const element of [subject, other]) {
|
9
|
+
if (!isHex(element)) {
|
10
|
+
this.assert(
|
11
|
+
isNegated, // trick to make this assertion always fail
|
12
|
+
`Expected "${subject}" to be a hex string equal to "${other}", but "${element}" is not a valid hex string`,
|
13
|
+
`Expected "${subject}" not to be a hex string equal to "${other}", but "${element}" is not a valid hex string`,
|
14
|
+
);
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
// compare values
|
19
|
+
const extractNumeric = (hex: string) => hex.replace(/^0x0*/, "");
|
20
|
+
this.assert(
|
21
|
+
extractNumeric(subject.toLowerCase()) ===
|
22
|
+
extractNumeric(other.toLowerCase()),
|
23
|
+
`Expected "${subject}" to be a hex string equal to "${other}"`,
|
24
|
+
`Expected "${subject}" NOT to be a hex string equal to "${other}", but it was`,
|
25
|
+
`Hex string representing the same number as ${other}`,
|
26
|
+
subject,
|
27
|
+
);
|
28
|
+
});
|
29
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
export function supportProperAddress(Assertion: Chai.AssertionStatic): void {
|
2
|
+
Assertion.addProperty("properAddress", function (this: any) {
|
3
|
+
const subject = this._obj;
|
4
|
+
this.assert(
|
5
|
+
/^0x[0-9a-fA-F]{40}$/.test(subject),
|
6
|
+
`Expected "${subject}" to be a proper address`,
|
7
|
+
`Expected "${subject}" NOT to be a proper address`,
|
8
|
+
"proper address (eg.: 0x1234567890123456789012345678901234567890)",
|
9
|
+
subject,
|
10
|
+
);
|
11
|
+
});
|
12
|
+
}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
export function supportProperHex(Assertion: Chai.AssertionStatic): void {
|
2
|
+
Assertion.addMethod("properHex", function (this: any, length: number) {
|
3
|
+
const subject = this._obj;
|
4
|
+
const isNegated = this.__flags.negate === true;
|
5
|
+
|
6
|
+
const isHex = (a: string) => /^0x[0-9a-fA-F]*$/.test(a);
|
7
|
+
if (!isHex(subject)) {
|
8
|
+
this.assert(
|
9
|
+
isNegated, // trick to make this assertion always fail
|
10
|
+
`Expected "${subject}" to be a proper hex string, but it contains invalid (non-hex) characters`,
|
11
|
+
`Expected "${subject}" NOT to be a proper hex string, but it contains only valid hex characters`,
|
12
|
+
);
|
13
|
+
}
|
14
|
+
|
15
|
+
this.assert(
|
16
|
+
subject.length === length + 2,
|
17
|
+
`Expected "${subject}" to be a hex string of length ${
|
18
|
+
length + 2
|
19
|
+
} (the provided ${length} plus 2 more for the "0x" prefix), but its length is ${
|
20
|
+
subject.length
|
21
|
+
}`,
|
22
|
+
`Expected "${subject}" NOT to be a hex string of length ${
|
23
|
+
length + 2
|
24
|
+
} (the provided ${length} plus 2 more for the "0x" prefix), but its length is ${
|
25
|
+
subject.length
|
26
|
+
}`,
|
27
|
+
);
|
28
|
+
});
|
29
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
export function supportProperPrivateKey(Assertion: Chai.AssertionStatic): void {
|
2
|
+
Assertion.addProperty("properPrivateKey", function (this: any) {
|
3
|
+
const subject = this._obj;
|
4
|
+
this.assert(
|
5
|
+
/^0x[0-9a-fA-F]{64}$/.test(subject),
|
6
|
+
`Expected "${subject}" to be a proper private key`,
|
7
|
+
`Expected "${subject}" NOT to be a proper private key`,
|
8
|
+
"proper private key (eg.: 0x1010101010101010101010101010101010101010101010101010101010101010)",
|
9
|
+
subject,
|
10
|
+
);
|
11
|
+
});
|
12
|
+
}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
export const PANIC_CODES = {
|
2
|
+
ASSERTION_ERROR: 0x1,
|
3
|
+
ARITHMETIC_OVERFLOW: 0x11,
|
4
|
+
DIVISION_BY_ZERO: 0x12,
|
5
|
+
ENUM_CONVERSION_OUT_OF_BOUNDS: 0x21,
|
6
|
+
INCORRECTLY_ENCODED_STORAGE_BYTE_ARRAY: 0x22,
|
7
|
+
POP_ON_EMPTY_ARRAY: 0x31,
|
8
|
+
ARRAY_ACCESS_OUT_OF_BOUNDS: 0x32,
|
9
|
+
TOO_MUCH_MEMORY_ALLOCATED: 0x41,
|
10
|
+
ZERO_INITIALIZED_VARIABLE: 0x51,
|
11
|
+
};
|
12
|
+
|
13
|
+
export function panicErrorCodeToReason(errorCode: bigint): string | undefined {
|
14
|
+
switch (errorCode) {
|
15
|
+
case 0x1n:
|
16
|
+
return "Assertion error";
|
17
|
+
case 0x11n:
|
18
|
+
return "Arithmetic operation overflowed outside of an unchecked block";
|
19
|
+
case 0x12n:
|
20
|
+
return "Division or modulo division by zero";
|
21
|
+
case 0x21n:
|
22
|
+
return "Tried to convert a value into an enum, but the value was too big or negative";
|
23
|
+
case 0x22n:
|
24
|
+
return "Incorrectly encoded storage byte array";
|
25
|
+
case 0x31n:
|
26
|
+
return ".pop() was called on an empty array";
|
27
|
+
case 0x32n:
|
28
|
+
return "Array accessed at an out-of-bounds or negative index";
|
29
|
+
case 0x41n:
|
30
|
+
return "Too much memory was allocated, or an array was created that is too large";
|
31
|
+
case 0x51n:
|
32
|
+
return "Called a zero-initialized variable of internal function type";
|
33
|
+
default:
|
34
|
+
return undefined;
|
35
|
+
}
|
36
|
+
}
|
@@ -0,0 +1,165 @@
|
|
1
|
+
import type { HardhatEthers } from "@nomicfoundation/hardhat-ethers/types";
|
2
|
+
|
3
|
+
import { HardhatError } from "@nomicfoundation/hardhat-errors";
|
4
|
+
import { numberToHexString } from "@nomicfoundation/hardhat-utils/hex";
|
5
|
+
|
6
|
+
import { REVERTED_MATCHER } from "../../constants.js";
|
7
|
+
import { assertIsNotNull } from "../../utils/asserts.js";
|
8
|
+
import { buildAssert } from "../../utils/build-assert.js";
|
9
|
+
import { preventAsyncMatcherChaining } from "../../utils/prevent-chaining.js";
|
10
|
+
|
11
|
+
import {
|
12
|
+
decodeReturnData,
|
13
|
+
getReturnDataFromError,
|
14
|
+
parseBytes32String,
|
15
|
+
} from "./utils.js";
|
16
|
+
|
17
|
+
export function supportReverted(
|
18
|
+
Assertion: Chai.AssertionStatic,
|
19
|
+
chaiUtils: Chai.ChaiUtils,
|
20
|
+
): void {
|
21
|
+
Assertion.addMethod(
|
22
|
+
REVERTED_MATCHER,
|
23
|
+
function (this: any, ethers: HardhatEthers) {
|
24
|
+
// capture negated flag before async code executes; see buildAssert's jsdoc
|
25
|
+
const negated = this.__flags.negate;
|
26
|
+
|
27
|
+
const subject: unknown = this._obj;
|
28
|
+
|
29
|
+
preventAsyncMatcherChaining(this, REVERTED_MATCHER, chaiUtils);
|
30
|
+
|
31
|
+
// Check if the received value can be linked to a transaction, and then
|
32
|
+
// get the receipt of that transaction and check its status.
|
33
|
+
//
|
34
|
+
// If the value doesn't correspond to a transaction, then the `reverted`
|
35
|
+
// assertion is false.
|
36
|
+
const onSuccess = async (value: unknown) => {
|
37
|
+
const assert = buildAssert(negated, onSuccess);
|
38
|
+
|
39
|
+
if (isTransactionResponse(value) || typeof value === "string") {
|
40
|
+
const hash = typeof value === "string" ? value : value.hash;
|
41
|
+
|
42
|
+
if (!isValidTransactionHash(hash)) {
|
43
|
+
throw new HardhatError(
|
44
|
+
HardhatError.ERRORS.CHAI_MATCHERS.EXPECTED_VALID_TRANSACTION_HASH,
|
45
|
+
{
|
46
|
+
hash,
|
47
|
+
},
|
48
|
+
);
|
49
|
+
}
|
50
|
+
|
51
|
+
const receipt = await getTransactionReceipt(ethers, hash);
|
52
|
+
|
53
|
+
if (receipt === null) {
|
54
|
+
// If the receipt is null, maybe the string is a bytes32 string
|
55
|
+
if (isBytes32String(hash)) {
|
56
|
+
assert(false, "Expected transaction to be reverted");
|
57
|
+
return;
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
assertIsNotNull(receipt, "receipt");
|
62
|
+
assert(
|
63
|
+
receipt.status === 0,
|
64
|
+
"Expected transaction to be reverted",
|
65
|
+
"Expected transaction NOT to be reverted",
|
66
|
+
);
|
67
|
+
} else if (isTransactionReceipt(value)) {
|
68
|
+
const receipt = value;
|
69
|
+
|
70
|
+
assert(
|
71
|
+
receipt.status === 0,
|
72
|
+
"Expected transaction to be reverted",
|
73
|
+
"Expected transaction NOT to be reverted",
|
74
|
+
);
|
75
|
+
} else {
|
76
|
+
// If the subject of the assertion is not connected to a transaction
|
77
|
+
// (hash, receipt, etc.), then the assertion fails.
|
78
|
+
// Since we use `false` here, this means that `.not.to.be.reverted`
|
79
|
+
// assertions will pass instead of always throwing a validation error.
|
80
|
+
// This allows users to do things like:
|
81
|
+
// `expect(c.callStatic.f()).to.not.be.reverted`
|
82
|
+
assert(false, "Expected transaction to be reverted");
|
83
|
+
}
|
84
|
+
};
|
85
|
+
|
86
|
+
const onError = (error: any) => {
|
87
|
+
const assert = buildAssert(negated, onError);
|
88
|
+
const returnData = getReturnDataFromError(error);
|
89
|
+
const decodedReturnData = decodeReturnData(returnData);
|
90
|
+
|
91
|
+
if (
|
92
|
+
decodedReturnData.kind === "Empty" ||
|
93
|
+
decodedReturnData.kind === "Custom"
|
94
|
+
) {
|
95
|
+
// in the negated case, if we can't decode the reason, we just indicate
|
96
|
+
// that the transaction didn't revert
|
97
|
+
assert(true, undefined, `Expected transaction NOT to be reverted`);
|
98
|
+
} else if (decodedReturnData.kind === "Error") {
|
99
|
+
assert(
|
100
|
+
true,
|
101
|
+
undefined,
|
102
|
+
`Expected transaction NOT to be reverted, but it reverted with reason '${decodedReturnData.reason}'`,
|
103
|
+
);
|
104
|
+
} else if (decodedReturnData.kind === "Panic") {
|
105
|
+
assert(
|
106
|
+
true,
|
107
|
+
undefined,
|
108
|
+
`Expected transaction NOT to be reverted, but it reverted with panic code ${numberToHexString(
|
109
|
+
decodedReturnData.code,
|
110
|
+
)} (${decodedReturnData.description})`,
|
111
|
+
);
|
112
|
+
} else {
|
113
|
+
const _exhaustiveCheck: never = decodedReturnData;
|
114
|
+
}
|
115
|
+
};
|
116
|
+
|
117
|
+
// we use `Promise.resolve(subject)` so we can process both values and
|
118
|
+
// promises of values in the same way
|
119
|
+
const derivedPromise = Promise.resolve(subject).then(onSuccess, onError);
|
120
|
+
|
121
|
+
this.then = derivedPromise.then.bind(derivedPromise);
|
122
|
+
this.catch = derivedPromise.catch.bind(derivedPromise);
|
123
|
+
|
124
|
+
return this;
|
125
|
+
},
|
126
|
+
);
|
127
|
+
}
|
128
|
+
|
129
|
+
async function getTransactionReceipt(ethers: HardhatEthers, hash: string) {
|
130
|
+
return ethers.provider.getTransactionReceipt(hash);
|
131
|
+
}
|
132
|
+
|
133
|
+
function isTransactionResponse(x: unknown): x is { hash: string } {
|
134
|
+
if (typeof x === "object" && x !== null) {
|
135
|
+
return "hash" in x;
|
136
|
+
}
|
137
|
+
|
138
|
+
return false;
|
139
|
+
}
|
140
|
+
|
141
|
+
function isTransactionReceipt(x: unknown): x is { status: number } {
|
142
|
+
if (typeof x === "object" && x !== null && "status" in x) {
|
143
|
+
const status = x.status;
|
144
|
+
|
145
|
+
// this means we only support ethers's receipts for now; adding support for
|
146
|
+
// raw receipts, where the status is an hexadecimal string, should be easy
|
147
|
+
// and we can do it if there's demand for that
|
148
|
+
return typeof status === "number";
|
149
|
+
}
|
150
|
+
|
151
|
+
return false;
|
152
|
+
}
|
153
|
+
|
154
|
+
function isValidTransactionHash(x: string): boolean {
|
155
|
+
return /0x[0-9a-fA-F]{64}/.test(x);
|
156
|
+
}
|
157
|
+
|
158
|
+
function isBytes32String(v: string): boolean {
|
159
|
+
try {
|
160
|
+
parseBytes32String(v);
|
161
|
+
return true;
|
162
|
+
} catch {
|
163
|
+
return false;
|
164
|
+
}
|
165
|
+
}
|
@@ -0,0 +1,100 @@
|
|
1
|
+
import { HardhatError } from "@nomicfoundation/hardhat-errors";
|
2
|
+
import { numberToHexString } from "@nomicfoundation/hardhat-utils/hex";
|
3
|
+
|
4
|
+
import { REVERTED_WITH_MATCHER } from "../../constants.js";
|
5
|
+
import { buildAssert } from "../../utils/build-assert.js";
|
6
|
+
import { preventAsyncMatcherChaining } from "../../utils/prevent-chaining.js";
|
7
|
+
|
8
|
+
import { decodeReturnData, getReturnDataFromError } from "./utils.js";
|
9
|
+
|
10
|
+
export function supportRevertedWith(
|
11
|
+
Assertion: Chai.AssertionStatic,
|
12
|
+
chaiUtils: Chai.ChaiUtils,
|
13
|
+
): void {
|
14
|
+
Assertion.addMethod(
|
15
|
+
REVERTED_WITH_MATCHER,
|
16
|
+
function (this: any, expectedReason: string | RegExp) {
|
17
|
+
// capture negated flag before async code executes; see buildAssert's jsdoc
|
18
|
+
const negated = this.__flags.negate;
|
19
|
+
|
20
|
+
// validate expected reason
|
21
|
+
if (
|
22
|
+
!(expectedReason instanceof RegExp) &&
|
23
|
+
typeof expectedReason !== "string"
|
24
|
+
) {
|
25
|
+
// if the input validation fails, we discard the subject since it could
|
26
|
+
// potentially be a rejected promise
|
27
|
+
Promise.resolve(this._obj).catch(() => {});
|
28
|
+
|
29
|
+
throw new HardhatError(
|
30
|
+
HardhatError.ERRORS.CHAI_MATCHERS.EXPECT_STRING_OR_REGEX_AS_REVERT_REASON,
|
31
|
+
);
|
32
|
+
}
|
33
|
+
|
34
|
+
const expectedReasonString =
|
35
|
+
expectedReason instanceof RegExp
|
36
|
+
? expectedReason.source
|
37
|
+
: expectedReason;
|
38
|
+
|
39
|
+
preventAsyncMatcherChaining(this, REVERTED_WITH_MATCHER, chaiUtils);
|
40
|
+
|
41
|
+
const onSuccess = () => {
|
42
|
+
const assert = buildAssert(negated, onSuccess);
|
43
|
+
|
44
|
+
assert(
|
45
|
+
false,
|
46
|
+
`Expected transaction to be reverted with reason '${expectedReasonString}', but it didn't revert`,
|
47
|
+
);
|
48
|
+
};
|
49
|
+
|
50
|
+
const onError = (error: any) => {
|
51
|
+
const assert = buildAssert(negated, onError);
|
52
|
+
|
53
|
+
const returnData = getReturnDataFromError(error);
|
54
|
+
const decodedReturnData = decodeReturnData(returnData);
|
55
|
+
|
56
|
+
if (decodedReturnData.kind === "Empty") {
|
57
|
+
assert(
|
58
|
+
false,
|
59
|
+
`Expected transaction to be reverted with reason '${expectedReasonString}', but it reverted without a reason`,
|
60
|
+
);
|
61
|
+
} else if (decodedReturnData.kind === "Error") {
|
62
|
+
const matchesExpectedReason =
|
63
|
+
expectedReason instanceof RegExp
|
64
|
+
? expectedReason.test(decodedReturnData.reason)
|
65
|
+
: decodedReturnData.reason === expectedReasonString;
|
66
|
+
|
67
|
+
assert(
|
68
|
+
matchesExpectedReason,
|
69
|
+
`Expected transaction to be reverted with reason '${expectedReasonString}', but it reverted with reason '${decodedReturnData.reason}'`,
|
70
|
+
`Expected transaction NOT to be reverted with reason '${expectedReasonString}', but it was`,
|
71
|
+
);
|
72
|
+
} else if (decodedReturnData.kind === "Panic") {
|
73
|
+
assert(
|
74
|
+
false,
|
75
|
+
`Expected transaction to be reverted with reason '${expectedReasonString}', but it reverted with panic code ${numberToHexString(
|
76
|
+
decodedReturnData.code,
|
77
|
+
)} (${decodedReturnData.description})`,
|
78
|
+
);
|
79
|
+
} else if (decodedReturnData.kind === "Custom") {
|
80
|
+
assert(
|
81
|
+
false,
|
82
|
+
`Expected transaction to be reverted with reason '${expectedReasonString}', but it reverted with a custom error`,
|
83
|
+
);
|
84
|
+
} else {
|
85
|
+
const _exhaustiveCheck: never = decodedReturnData;
|
86
|
+
}
|
87
|
+
};
|
88
|
+
|
89
|
+
const derivedPromise = Promise.resolve(this._obj).then(
|
90
|
+
onSuccess,
|
91
|
+
onError,
|
92
|
+
);
|
93
|
+
|
94
|
+
this.then = derivedPromise.then.bind(derivedPromise);
|
95
|
+
this.catch = derivedPromise.catch.bind(derivedPromise);
|
96
|
+
|
97
|
+
return this;
|
98
|
+
},
|
99
|
+
);
|
100
|
+
}
|