@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.
Files changed (173) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +52 -0
  3. package/dist/src/index.d.ts +5 -0
  4. package/dist/src/index.d.ts.map +1 -0
  5. package/dist/src/index.js +16 -0
  6. package/dist/src/index.js.map +1 -0
  7. package/dist/src/internal/add-chai-matchers.d.ts +2 -0
  8. package/dist/src/internal/add-chai-matchers.d.ts.map +1 -0
  9. package/dist/src/internal/add-chai-matchers.js +41 -0
  10. package/dist/src/internal/add-chai-matchers.js.map +1 -0
  11. package/dist/src/internal/constants.d.ts +13 -0
  12. package/dist/src/internal/constants.d.ts.map +1 -0
  13. package/dist/src/internal/constants.js +13 -0
  14. package/dist/src/internal/constants.js.map +1 -0
  15. package/dist/src/internal/hook-handlers/network.d.ts +4 -0
  16. package/dist/src/internal/hook-handlers/network.d.ts.map +1 -0
  17. package/dist/src/internal/hook-handlers/network.js +15 -0
  18. package/dist/src/internal/hook-handlers/network.js.map +1 -0
  19. package/dist/src/internal/matchers/addressable.d.ts +2 -0
  20. package/dist/src/internal/matchers/addressable.d.ts.map +1 -0
  21. package/dist/src/internal/matchers/addressable.js +53 -0
  22. package/dist/src/internal/matchers/addressable.js.map +1 -0
  23. package/dist/src/internal/matchers/big-number.d.ts +2 -0
  24. package/dist/src/internal/matchers/big-number.d.ts.map +1 -0
  25. package/dist/src/internal/matchers/big-number.js +178 -0
  26. package/dist/src/internal/matchers/big-number.js.map +1 -0
  27. package/dist/src/internal/matchers/changeEtherBalance.d.ts +7 -0
  28. package/dist/src/internal/matchers/changeEtherBalance.d.ts.map +1 -0
  29. package/dist/src/internal/matchers/changeEtherBalance.js +77 -0
  30. package/dist/src/internal/matchers/changeEtherBalance.js.map +1 -0
  31. package/dist/src/internal/matchers/changeEtherBalances.d.ts +7 -0
  32. package/dist/src/internal/matchers/changeEtherBalances.d.ts.map +1 -0
  33. package/dist/src/internal/matchers/changeEtherBalances.js +96 -0
  34. package/dist/src/internal/matchers/changeEtherBalances.js.map +1 -0
  35. package/dist/src/internal/matchers/changeTokenBalance.d.ts +16 -0
  36. package/dist/src/internal/matchers/changeTokenBalance.d.ts.map +1 -0
  37. package/dist/src/internal/matchers/changeTokenBalance.js +148 -0
  38. package/dist/src/internal/matchers/changeTokenBalance.js.map +1 -0
  39. package/dist/src/internal/matchers/emit.d.ts +5 -0
  40. package/dist/src/internal/matchers/emit.d.ts.map +1 -0
  41. package/dist/src/internal/matchers/emit.js +122 -0
  42. package/dist/src/internal/matchers/emit.js.map +1 -0
  43. package/dist/src/internal/matchers/hexEqual.d.ts +2 -0
  44. package/dist/src/internal/matchers/hexEqual.d.ts.map +1 -0
  45. package/dist/src/internal/matchers/hexEqual.js +19 -0
  46. package/dist/src/internal/matchers/hexEqual.js.map +1 -0
  47. package/dist/src/internal/matchers/properAddress.d.ts +2 -0
  48. package/dist/src/internal/matchers/properAddress.d.ts.map +1 -0
  49. package/dist/src/internal/matchers/properAddress.js +7 -0
  50. package/dist/src/internal/matchers/properAddress.js.map +1 -0
  51. package/dist/src/internal/matchers/properHex.d.ts +2 -0
  52. package/dist/src/internal/matchers/properHex.d.ts.map +1 -0
  53. package/dist/src/internal/matchers/properHex.js +13 -0
  54. package/dist/src/internal/matchers/properHex.js.map +1 -0
  55. package/dist/src/internal/matchers/properPrivateKey.d.ts +2 -0
  56. package/dist/src/internal/matchers/properPrivateKey.d.ts.map +1 -0
  57. package/dist/src/internal/matchers/properPrivateKey.js +7 -0
  58. package/dist/src/internal/matchers/properPrivateKey.js.map +1 -0
  59. package/dist/src/internal/matchers/reverted/panic.d.ts +13 -0
  60. package/dist/src/internal/matchers/reverted/panic.d.ts.map +1 -0
  61. package/dist/src/internal/matchers/reverted/panic.js +36 -0
  62. package/dist/src/internal/matchers/reverted/panic.js.map +1 -0
  63. package/dist/src/internal/matchers/reverted/reverted.d.ts +2 -0
  64. package/dist/src/internal/matchers/reverted/reverted.d.ts.map +1 -0
  65. package/dist/src/internal/matchers/reverted/reverted.js +112 -0
  66. package/dist/src/internal/matchers/reverted/reverted.js.map +1 -0
  67. package/dist/src/internal/matchers/reverted/revertedWith.d.ts +2 -0
  68. package/dist/src/internal/matchers/reverted/revertedWith.d.ts.map +1 -0
  69. package/dist/src/internal/matchers/reverted/revertedWith.js +56 -0
  70. package/dist/src/internal/matchers/reverted/revertedWith.js.map +1 -0
  71. package/dist/src/internal/matchers/reverted/revertedWithCustomError.d.ts +5 -0
  72. package/dist/src/internal/matchers/reverted/revertedWithCustomError.d.ts.map +1 -0
  73. package/dist/src/internal/matchers/reverted/revertedWithCustomError.js +120 -0
  74. package/dist/src/internal/matchers/reverted/revertedWithCustomError.js.map +1 -0
  75. package/dist/src/internal/matchers/reverted/revertedWithPanic.d.ts +2 -0
  76. package/dist/src/internal/matchers/reverted/revertedWithPanic.d.ts.map +1 -0
  77. package/dist/src/internal/matchers/reverted/revertedWithPanic.js +74 -0
  78. package/dist/src/internal/matchers/reverted/revertedWithPanic.js.map +1 -0
  79. package/dist/src/internal/matchers/reverted/revertedWithoutReason.d.ts +2 -0
  80. package/dist/src/internal/matchers/reverted/revertedWithoutReason.d.ts.map +1 -0
  81. package/dist/src/internal/matchers/reverted/revertedWithoutReason.js +41 -0
  82. package/dist/src/internal/matchers/reverted/revertedWithoutReason.js.map +1 -0
  83. package/dist/src/internal/matchers/reverted/utils.d.ts +39 -0
  84. package/dist/src/internal/matchers/reverted/utils.d.ts.map +1 -0
  85. package/dist/src/internal/matchers/reverted/utils.js +108 -0
  86. package/dist/src/internal/matchers/reverted/utils.js.map +1 -0
  87. package/dist/src/internal/matchers/withArgs.d.ts +16 -0
  88. package/dist/src/internal/matchers/withArgs.d.ts.map +1 -0
  89. package/dist/src/internal/matchers/withArgs.js +88 -0
  90. package/dist/src/internal/matchers/withArgs.js.map +1 -0
  91. package/dist/src/internal/utils/account.d.ts +3 -0
  92. package/dist/src/internal/utils/account.d.ts.map +1 -0
  93. package/dist/src/internal/utils/account.js +15 -0
  94. package/dist/src/internal/utils/account.js.map +1 -0
  95. package/dist/src/internal/utils/asserts.d.ts +5 -0
  96. package/dist/src/internal/utils/asserts.d.ts.map +1 -0
  97. package/dist/src/internal/utils/asserts.js +73 -0
  98. package/dist/src/internal/utils/asserts.js.map +1 -0
  99. package/dist/src/internal/utils/balance.d.ts +8 -0
  100. package/dist/src/internal/utils/balance.d.ts.map +1 -0
  101. package/dist/src/internal/utils/balance.js +19 -0
  102. package/dist/src/internal/utils/balance.js.map +1 -0
  103. package/dist/src/internal/utils/bigint.d.ts +2 -0
  104. package/dist/src/internal/utils/bigint.d.ts.map +1 -0
  105. package/dist/src/internal/utils/bigint.js +4 -0
  106. package/dist/src/internal/utils/bigint.js.map +1 -0
  107. package/dist/src/internal/utils/build-assert.d.ts +19 -0
  108. package/dist/src/internal/utils/build-assert.d.ts.map +1 -0
  109. package/dist/src/internal/utils/build-assert.js +39 -0
  110. package/dist/src/internal/utils/build-assert.js.map +1 -0
  111. package/dist/src/internal/utils/ordinal.d.ts +8 -0
  112. package/dist/src/internal/utils/ordinal.d.ts.map +1 -0
  113. package/dist/src/internal/utils/ordinal.js +21 -0
  114. package/dist/src/internal/utils/ordinal.js.map +1 -0
  115. package/dist/src/internal/utils/prevent-chaining.d.ts +2 -0
  116. package/dist/src/internal/utils/prevent-chaining.d.ts.map +1 -0
  117. package/dist/src/internal/utils/prevent-chaining.js +17 -0
  118. package/dist/src/internal/utils/prevent-chaining.js.map +1 -0
  119. package/dist/src/internal/utils/ssfi.d.ts +4 -0
  120. package/dist/src/internal/utils/ssfi.d.ts.map +1 -0
  121. package/dist/src/internal/utils/ssfi.js +2 -0
  122. package/dist/src/internal/utils/ssfi.js.map +1 -0
  123. package/dist/src/internal/utils/typed.d.ts +2 -0
  124. package/dist/src/internal/utils/typed.d.ts.map +1 -0
  125. package/dist/src/internal/utils/typed.js +10 -0
  126. package/dist/src/internal/utils/typed.js.map +1 -0
  127. package/dist/src/panic.d.ts +2 -0
  128. package/dist/src/panic.d.ts.map +1 -0
  129. package/dist/src/panic.js +2 -0
  130. package/dist/src/panic.js.map +1 -0
  131. package/dist/src/type-extensions.d.ts +45 -0
  132. package/dist/src/type-extensions.d.ts.map +1 -0
  133. package/dist/src/type-extensions.js +2 -0
  134. package/dist/src/type-extensions.js.map +1 -0
  135. package/dist/src/withArgs.d.ts +2 -0
  136. package/dist/src/withArgs.d.ts.map +1 -0
  137. package/dist/src/withArgs.js +2 -0
  138. package/dist/src/withArgs.js.map +1 -0
  139. package/package.json +85 -0
  140. package/src/index.ts +21 -0
  141. package/src/internal/add-chai-matchers.ts +46 -0
  142. package/src/internal/constants.ts +13 -0
  143. package/src/internal/hook-handlers/network.ts +24 -0
  144. package/src/internal/matchers/addressable.ts +86 -0
  145. package/src/internal/matchers/big-number.ts +279 -0
  146. package/src/internal/matchers/changeEtherBalance.ts +138 -0
  147. package/src/internal/matchers/changeEtherBalances.ts +188 -0
  148. package/src/internal/matchers/changeTokenBalance.ts +295 -0
  149. package/src/internal/matchers/emit.ts +232 -0
  150. package/src/internal/matchers/hexEqual.ts +29 -0
  151. package/src/internal/matchers/properAddress.ts +12 -0
  152. package/src/internal/matchers/properHex.ts +29 -0
  153. package/src/internal/matchers/properPrivateKey.ts +12 -0
  154. package/src/internal/matchers/reverted/panic.ts +36 -0
  155. package/src/internal/matchers/reverted/reverted.ts +165 -0
  156. package/src/internal/matchers/reverted/revertedWith.ts +100 -0
  157. package/src/internal/matchers/reverted/revertedWithCustomError.ts +243 -0
  158. package/src/internal/matchers/reverted/revertedWithPanic.ts +118 -0
  159. package/src/internal/matchers/reverted/revertedWithoutReason.ts +73 -0
  160. package/src/internal/matchers/reverted/utils.ts +147 -0
  161. package/src/internal/matchers/withArgs.ts +139 -0
  162. package/src/internal/utils/account.ts +24 -0
  163. package/src/internal/utils/asserts.ts +156 -0
  164. package/src/internal/utils/balance.ts +39 -0
  165. package/src/internal/utils/bigint.ts +3 -0
  166. package/src/internal/utils/build-assert.ts +54 -0
  167. package/src/internal/utils/ordinal.ts +24 -0
  168. package/src/internal/utils/prevent-chaining.ts +33 -0
  169. package/src/internal/utils/ssfi.ts +6 -0
  170. package/src/internal/utils/typed.ts +9 -0
  171. package/src/panic.ts +1 -0
  172. package/src/type-extensions.ts +82 -0
  173. 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
+ }