hardhat 3.4.5 → 3.5.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 (215) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/dist/src/config.d.ts +0 -1
  3. package/dist/src/config.d.ts.map +1 -1
  4. package/dist/src/config.js.map +1 -1
  5. package/dist/src/hre.d.ts +0 -1
  6. package/dist/src/hre.d.ts.map +1 -1
  7. package/dist/src/hre.js.map +1 -1
  8. package/dist/src/index.d.ts +0 -1
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/index.js.map +1 -1
  11. package/dist/src/internal/builtin-global-options.d.ts.map +1 -1
  12. package/dist/src/internal/builtin-global-options.js +1 -1
  13. package/dist/src/internal/builtin-global-options.js.map +1 -1
  14. package/dist/src/internal/builtin-plugins/gas-analytics/snapshot-cheatcodes.d.ts +21 -0
  15. package/dist/src/internal/builtin-plugins/gas-analytics/snapshot-cheatcodes.d.ts.map +1 -1
  16. package/dist/src/internal/builtin-plugins/gas-analytics/snapshot-cheatcodes.js +49 -1
  17. package/dist/src/internal/builtin-plugins/gas-analytics/snapshot-cheatcodes.js.map +1 -1
  18. package/dist/src/internal/builtin-plugins/gas-analytics/tasks/solidity-test/task-action.d.ts +2 -1
  19. package/dist/src/internal/builtin-plugins/gas-analytics/tasks/solidity-test/task-action.d.ts.map +1 -1
  20. package/dist/src/internal/builtin-plugins/gas-analytics/tasks/solidity-test/task-action.js +12 -2
  21. package/dist/src/internal/builtin-plugins/gas-analytics/tasks/solidity-test/task-action.js.map +1 -1
  22. package/dist/src/internal/builtin-plugins/network-manager/config-resolution.d.ts.map +1 -1
  23. package/dist/src/internal/builtin-plugins/network-manager/config-resolution.js +9 -2
  24. package/dist/src/internal/builtin-plugins/network-manager/config-resolution.js.map +1 -1
  25. package/dist/src/internal/builtin-plugins/network-manager/edr/edr-constants.d.ts +1 -0
  26. package/dist/src/internal/builtin-plugins/network-manager/edr/edr-constants.d.ts.map +1 -1
  27. package/dist/src/internal/builtin-plugins/network-manager/edr/edr-constants.js +1 -0
  28. package/dist/src/internal/builtin-plugins/network-manager/edr/edr-constants.js.map +1 -1
  29. package/dist/src/internal/builtin-plugins/network-manager/edr/edr-provider.d.ts.map +1 -1
  30. package/dist/src/internal/builtin-plugins/network-manager/edr/edr-provider.js +25 -5
  31. package/dist/src/internal/builtin-plugins/network-manager/edr/edr-provider.js.map +1 -1
  32. package/dist/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.d.ts +16 -0
  33. package/dist/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.d.ts.map +1 -1
  34. package/dist/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.js +28 -1
  35. package/dist/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.js.map +1 -1
  36. package/dist/src/internal/builtin-plugins/network-manager/network-manager.d.ts.map +1 -1
  37. package/dist/src/internal/builtin-plugins/network-manager/network-manager.js +2 -6
  38. package/dist/src/internal/builtin-plugins/network-manager/network-manager.js.map +1 -1
  39. package/dist/src/internal/builtin-plugins/network-manager/type-extensions/config.d.ts +5 -3
  40. package/dist/src/internal/builtin-plugins/network-manager/type-extensions/config.d.ts.map +1 -1
  41. package/dist/src/internal/builtin-plugins/network-manager/type-validation.d.ts.map +1 -1
  42. package/dist/src/internal/builtin-plugins/network-manager/type-validation.js +3 -1
  43. package/dist/src/internal/builtin-plugins/network-manager/type-validation.js.map +1 -1
  44. package/dist/src/internal/builtin-plugins/network-manager/utils/apply-coverage-network-overrides.d.ts +18 -0
  45. package/dist/src/internal/builtin-plugins/network-manager/utils/apply-coverage-network-overrides.d.ts.map +1 -0
  46. package/dist/src/internal/builtin-plugins/network-manager/utils/apply-coverage-network-overrides.js +27 -0
  47. package/dist/src/internal/builtin-plugins/network-manager/utils/apply-coverage-network-overrides.js.map +1 -0
  48. package/dist/src/internal/builtin-plugins/node/json-rpc/handler.js +6 -5
  49. package/dist/src/internal/builtin-plugins/node/json-rpc/handler.js.map +1 -1
  50. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/remapped-npm-packages-graph.d.ts.map +1 -1
  51. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/remapped-npm-packages-graph.js +1 -1
  52. package/dist/src/internal/builtin-plugins/solidity/build-system/resolver/remapped-npm-packages-graph.js.map +1 -1
  53. package/dist/src/internal/builtin-plugins/solidity/constants.d.ts +10 -0
  54. package/dist/src/internal/builtin-plugins/solidity/constants.d.ts.map +1 -1
  55. package/dist/src/internal/builtin-plugins/solidity/constants.js +10 -0
  56. package/dist/src/internal/builtin-plugins/solidity/constants.js.map +1 -1
  57. package/dist/src/internal/builtin-plugins/solidity/index.d.ts.map +1 -1
  58. package/dist/src/internal/builtin-plugins/solidity/index.js +1 -0
  59. package/dist/src/internal/builtin-plugins/solidity/index.js.map +1 -1
  60. package/dist/src/internal/builtin-plugins/solidity/tasks/compile.d.ts +9 -0
  61. package/dist/src/internal/builtin-plugins/solidity/tasks/compile.d.ts.map +1 -0
  62. package/dist/src/internal/builtin-plugins/solidity/tasks/compile.js +10 -0
  63. package/dist/src/internal/builtin-plugins/solidity/tasks/compile.js.map +1 -0
  64. package/dist/src/internal/builtin-plugins/solidity-test/config.d.ts +4 -3
  65. package/dist/src/internal/builtin-plugins/solidity-test/config.d.ts.map +1 -1
  66. package/dist/src/internal/builtin-plugins/solidity-test/config.js +55 -8
  67. package/dist/src/internal/builtin-plugins/solidity-test/config.js.map +1 -1
  68. package/dist/src/internal/builtin-plugins/solidity-test/eip712/ast-walker.d.ts +58 -0
  69. package/dist/src/internal/builtin-plugins/solidity-test/eip712/ast-walker.d.ts.map +1 -0
  70. package/dist/src/internal/builtin-plugins/solidity-test/eip712/ast-walker.js +226 -0
  71. package/dist/src/internal/builtin-plugins/solidity-test/eip712/ast-walker.js.map +1 -0
  72. package/dist/src/internal/builtin-plugins/solidity-test/eip712/canonicalize.d.ts +29 -0
  73. package/dist/src/internal/builtin-plugins/solidity-test/eip712/canonicalize.d.ts.map +1 -0
  74. package/dist/src/internal/builtin-plugins/solidity-test/eip712/canonicalize.js +244 -0
  75. package/dist/src/internal/builtin-plugins/solidity-test/eip712/canonicalize.js.map +1 -0
  76. package/dist/src/internal/builtin-plugins/solidity-test/eip712/glob.d.ts +7 -0
  77. package/dist/src/internal/builtin-plugins/solidity-test/eip712/glob.d.ts.map +1 -0
  78. package/dist/src/internal/builtin-plugins/solidity-test/eip712/glob.js +204 -0
  79. package/dist/src/internal/builtin-plugins/solidity-test/eip712/glob.js.map +1 -0
  80. package/dist/src/internal/builtin-plugins/solidity-test/eip712/index.d.ts +21 -0
  81. package/dist/src/internal/builtin-plugins/solidity-test/eip712/index.d.ts.map +1 -0
  82. package/dist/src/internal/builtin-plugins/solidity-test/eip712/index.js +77 -0
  83. package/dist/src/internal/builtin-plugins/solidity-test/eip712/index.js.map +1 -0
  84. package/dist/src/internal/builtin-plugins/solidity-test/helpers.d.ts +4 -3
  85. package/dist/src/internal/builtin-plugins/solidity-test/helpers.d.ts.map +1 -1
  86. package/dist/src/internal/builtin-plugins/solidity-test/helpers.js +20 -3
  87. package/dist/src/internal/builtin-plugins/solidity-test/helpers.js.map +1 -1
  88. package/dist/src/internal/builtin-plugins/solidity-test/task-action.d.ts.map +1 -1
  89. package/dist/src/internal/builtin-plugins/solidity-test/task-action.js +5 -1
  90. package/dist/src/internal/builtin-plugins/solidity-test/task-action.js.map +1 -1
  91. package/dist/src/internal/builtin-plugins/solidity-test/test-profiles.d.ts +2 -0
  92. package/dist/src/internal/builtin-plugins/solidity-test/test-profiles.d.ts.map +1 -0
  93. package/dist/src/internal/builtin-plugins/solidity-test/test-profiles.js +2 -0
  94. package/dist/src/internal/builtin-plugins/solidity-test/test-profiles.js.map +1 -0
  95. package/dist/src/internal/builtin-plugins/solidity-test/type-extensions.d.ts +91 -32
  96. package/dist/src/internal/builtin-plugins/solidity-test/type-extensions.d.ts.map +1 -1
  97. package/dist/src/internal/cli/init/init.d.ts +31 -6
  98. package/dist/src/internal/cli/init/init.d.ts.map +1 -1
  99. package/dist/src/internal/cli/init/init.js +121 -10
  100. package/dist/src/internal/cli/init/init.js.map +1 -1
  101. package/dist/src/internal/cli/main.d.ts.map +1 -1
  102. package/dist/src/internal/cli/main.js +41 -1
  103. package/dist/src/internal/cli/main.js.map +1 -1
  104. package/dist/src/types/arguments.d.ts +1 -0
  105. package/dist/src/types/arguments.d.ts.map +1 -1
  106. package/dist/src/types/arguments.js +1 -0
  107. package/dist/src/types/arguments.js.map +1 -1
  108. package/dist/src/types/artifacts.d.ts +1 -0
  109. package/dist/src/types/artifacts.d.ts.map +1 -1
  110. package/dist/src/types/artifacts.js +1 -1
  111. package/dist/src/types/artifacts.js.map +1 -1
  112. package/dist/src/types/builtin-plugin-type-extensions.d.ts +2 -0
  113. package/dist/src/types/builtin-plugin-type-extensions.d.ts.map +1 -0
  114. package/dist/src/types/builtin-plugin-type-extensions.js +2 -0
  115. package/dist/src/types/builtin-plugin-type-extensions.js.map +1 -0
  116. package/dist/src/types/config.d.ts +1 -0
  117. package/dist/src/types/config.d.ts.map +1 -1
  118. package/dist/src/types/config.js +1 -1
  119. package/dist/src/types/config.js.map +1 -1
  120. package/dist/src/types/global-options.d.ts +1 -0
  121. package/dist/src/types/global-options.d.ts.map +1 -1
  122. package/dist/src/types/global-options.js +1 -1
  123. package/dist/src/types/global-options.js.map +1 -1
  124. package/dist/src/types/hooks.d.ts +1 -0
  125. package/dist/src/types/hooks.d.ts.map +1 -1
  126. package/dist/src/types/hooks.js +1 -1
  127. package/dist/src/types/hooks.js.map +1 -1
  128. package/dist/src/types/hre.d.ts +1 -0
  129. package/dist/src/types/hre.d.ts.map +1 -1
  130. package/dist/src/types/hre.js +1 -1
  131. package/dist/src/types/hre.js.map +1 -1
  132. package/dist/src/types/index.d.ts +1 -0
  133. package/dist/src/types/index.d.ts.map +1 -1
  134. package/dist/src/types/index.js +1 -0
  135. package/dist/src/types/index.js.map +1 -1
  136. package/dist/src/types/network.d.ts +1 -0
  137. package/dist/src/types/network.d.ts.map +1 -1
  138. package/dist/src/types/network.js +1 -1
  139. package/dist/src/types/network.js.map +1 -1
  140. package/dist/src/types/plugins.d.ts +1 -1
  141. package/dist/src/types/plugins.d.ts.map +1 -1
  142. package/dist/src/types/plugins.js +1 -1
  143. package/dist/src/types/plugins.js.map +1 -1
  144. package/dist/src/types/providers.d.ts +1 -0
  145. package/dist/src/types/providers.d.ts.map +1 -1
  146. package/dist/src/types/providers.js +1 -1
  147. package/dist/src/types/providers.js.map +1 -1
  148. package/dist/src/types/solidity.d.ts +1 -0
  149. package/dist/src/types/solidity.d.ts.map +1 -1
  150. package/dist/src/types/solidity.js +1 -0
  151. package/dist/src/types/solidity.js.map +1 -1
  152. package/dist/src/types/tasks.d.ts +1 -0
  153. package/dist/src/types/tasks.d.ts.map +1 -1
  154. package/dist/src/types/tasks.js +1 -0
  155. package/dist/src/types/tasks.js.map +1 -1
  156. package/dist/src/types/test.d.ts +1 -0
  157. package/dist/src/types/test.d.ts.map +1 -1
  158. package/dist/src/types/test.js +1 -1
  159. package/dist/src/types/test.js.map +1 -1
  160. package/dist/src/types/user-interruptions.d.ts +1 -0
  161. package/dist/src/types/user-interruptions.d.ts.map +1 -1
  162. package/dist/src/types/user-interruptions.js +1 -1
  163. package/dist/src/types/user-interruptions.js.map +1 -1
  164. package/package.json +12 -11
  165. package/src/config.ts +0 -2
  166. package/src/hre.ts +0 -3
  167. package/src/index.ts +0 -3
  168. package/src/internal/builtin-global-options.ts +2 -1
  169. package/src/internal/builtin-plugins/gas-analytics/snapshot-cheatcodes.ts +90 -1
  170. package/src/internal/builtin-plugins/gas-analytics/tasks/solidity-test/task-action.ts +24 -2
  171. package/src/internal/builtin-plugins/network-manager/config-resolution.ts +11 -3
  172. package/src/internal/builtin-plugins/network-manager/edr/edr-constants.ts +2 -0
  173. package/src/internal/builtin-plugins/network-manager/edr/edr-provider.ts +38 -8
  174. package/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts +43 -1
  175. package/src/internal/builtin-plugins/network-manager/network-manager.ts +5 -6
  176. package/src/internal/builtin-plugins/network-manager/type-extensions/config.ts +5 -3
  177. package/src/internal/builtin-plugins/network-manager/type-validation.ts +7 -1
  178. package/src/internal/builtin-plugins/network-manager/utils/apply-coverage-network-overrides.ts +32 -0
  179. package/src/internal/builtin-plugins/node/json-rpc/handler.ts +7 -5
  180. package/src/internal/builtin-plugins/solidity/build-system/resolver/remapped-npm-packages-graph.ts +1 -2
  181. package/src/internal/builtin-plugins/solidity/constants.ts +11 -0
  182. package/src/internal/builtin-plugins/solidity/index.ts +1 -0
  183. package/src/internal/builtin-plugins/solidity/tasks/compile.ts +12 -0
  184. package/src/internal/builtin-plugins/solidity-test/config.ts +85 -12
  185. package/src/internal/builtin-plugins/solidity-test/eip712/ast-walker.ts +324 -0
  186. package/src/internal/builtin-plugins/solidity-test/eip712/canonicalize.ts +317 -0
  187. package/src/internal/builtin-plugins/solidity-test/eip712/glob.ts +225 -0
  188. package/src/internal/builtin-plugins/solidity-test/eip712/index.ts +120 -0
  189. package/src/internal/builtin-plugins/solidity-test/helpers.ts +28 -4
  190. package/src/internal/builtin-plugins/solidity-test/task-action.ts +12 -1
  191. package/src/internal/builtin-plugins/solidity-test/test-profiles.ts +1 -0
  192. package/src/internal/builtin-plugins/solidity-test/type-extensions.ts +100 -33
  193. package/src/internal/cli/init/init.ts +198 -16
  194. package/src/internal/cli/main.ts +64 -1
  195. package/src/types/arguments.ts +2 -0
  196. package/src/types/artifacts.ts +2 -0
  197. package/src/types/builtin-plugin-type-extensions.ts +15 -0
  198. package/src/types/config.ts +2 -0
  199. package/src/types/global-options.ts +2 -0
  200. package/src/types/hooks.ts +2 -0
  201. package/src/types/hre.ts +2 -0
  202. package/src/types/index.ts +2 -0
  203. package/src/types/network.ts +2 -0
  204. package/src/types/plugins.ts +1 -2
  205. package/src/types/providers.ts +2 -0
  206. package/src/types/solidity.ts +2 -0
  207. package/src/types/tasks.ts +2 -0
  208. package/src/types/test.ts +2 -0
  209. package/src/types/user-interruptions.ts +2 -0
  210. package/templates/hardhat-3/01-node-test-runner-viem/package.json +11 -11
  211. package/templates/hardhat-3/01-node-test-runner-viem/tsconfig.json +4 -7
  212. package/templates/hardhat-3/02-mocha-ethers/package.json +13 -13
  213. package/templates/hardhat-3/02-mocha-ethers/tsconfig.json +4 -7
  214. package/templates/hardhat-3/03-minimal/package.json +2 -2
  215. package/templates/hardhat-3/03-minimal/tsconfig.json +4 -7
@@ -0,0 +1,317 @@
1
+ import type { CollectedStruct } from "./ast-walker.js";
2
+
3
+ import { HardhatError } from "@nomicfoundation/hardhat-errors";
4
+
5
+ /**
6
+ * Produces the flat list of canonical EIP-712 type strings expected by EDR.
7
+ *
8
+ * Each encodable struct contributes one entry, built like this:
9
+ * 1. Start with the struct's own head: `Name(type1 name1,type2 name2,...)`.
10
+ * 2. If the struct has fields that reference other structs, append those
11
+ * structs' heads after it, sorted alphabetically.
12
+ *
13
+ * Examples:
14
+ * - `Person` has only primitive fields (address, string), so its entry is
15
+ * just its own head:
16
+ * `Person(address wallet,string name)`
17
+ * - `Mail` has a `Person` field, so its entry is its head plus `Person`'s
18
+ * head appended:
19
+ * `Mail(Person from,Person to,string contents)Person(address wallet,string name)`
20
+ *
21
+ * Structs that contain members whose type cannot be EIP-712 encoded (mappings,
22
+ * function types, etc.) are dropped entirely, along with any structs that
23
+ * depend on them transitively. This matches `forge bind-json`'s behavior:
24
+ * `resolve_struct_eip712` returns `None` for any struct containing unsupported
25
+ * constructs and propagates `None` through the dep graph so dependents are
26
+ * also dropped.
27
+ *
28
+ * Only names in `selectedNames` are emitted; non-selected structs still
29
+ * participate in dep resolution so cross-file deps inline correctly.
30
+ */
31
+ export function canonicalizeStructs(
32
+ structs: CollectedStruct[],
33
+ selectedNames: Set<string>,
34
+ ): string[] {
35
+ const byName = indexByName(structs, selectedNames);
36
+ const knownNames = new Set(byName.keys());
37
+ const encodable = computeEncodable(byName, knownNames);
38
+ const result: string[] = [];
39
+
40
+ for (const struct of byName.values()) {
41
+ if (!selectedNames.has(struct.name)) {
42
+ continue;
43
+ }
44
+
45
+ if (!encodable.has(struct.name)) {
46
+ continue;
47
+ }
48
+
49
+ const head = encodeStructHead(struct);
50
+ const deps = transitiveDeps(struct, byName)
51
+ .map((depName) => {
52
+ const def = byName.get(depName);
53
+ return def === undefined ? undefined : encodeStructHead(def);
54
+ })
55
+ .filter((s): s is string => s !== undefined);
56
+
57
+ deps.sort();
58
+
59
+ result.push(head + deps.join(""));
60
+ }
61
+
62
+ return result;
63
+ }
64
+
65
+ /**
66
+ * Returns the set of struct names that are EIP-712 encodable. A struct is
67
+ * encodable if none of its members has a non-decodable type (`type === undefined`,
68
+ * e.g. mappings or function types) AND every one of its struct deps — direct or
69
+ * transitive — is itself encodable.
70
+ */
71
+ function computeEncodable(
72
+ byName: Map<string, CollectedStruct>,
73
+ knownNames: Set<string>,
74
+ ): Set<string> {
75
+ const encodable = new Set<string>(byName.keys());
76
+
77
+ for (const [name, struct] of byName) {
78
+ if (struct.members.some((m) => m.type === undefined)) {
79
+ encodable.delete(name);
80
+ }
81
+ }
82
+
83
+ const depsByName = new Map<string, string[]>();
84
+ for (const [name, struct] of byName) {
85
+ depsByName.set(name, directStructDeps(struct, knownNames));
86
+ }
87
+
88
+ let changed = true;
89
+ while (changed) {
90
+ changed = false;
91
+ for (const name of [...encodable]) {
92
+ const deps = depsByName.get(name) ?? [];
93
+ if (deps.some((dep) => !encodable.has(dep))) {
94
+ encodable.delete(name);
95
+ changed = true;
96
+ }
97
+ }
98
+ }
99
+
100
+ return encodable;
101
+ }
102
+
103
+ /**
104
+ * Computes the EIP-712 `encodeType` string for one struct in isolation:
105
+ * `Name(type1 name1,type2 name2,...)`.
106
+ * Members whose type is `undefined` (e.g. mappings) are dropped.
107
+ */
108
+ function encodeStructHead(struct: CollectedStruct): string {
109
+ const memberSegments: string[] = [];
110
+ for (const member of struct.members) {
111
+ if (member.type === undefined) {
112
+ continue;
113
+ }
114
+
115
+ memberSegments.push(`${member.type} ${member.name}`);
116
+ }
117
+
118
+ return `${struct.name}(${memberSegments.join(",")})`;
119
+ }
120
+
121
+ /**
122
+ * Returns the names of all struct dependencies referenced by `struct`'s
123
+ * members. Considers the base type of arrays. Members whose base type does not
124
+ * resolve to a known struct (elementary types, address, etc.) are ignored.
125
+ */
126
+ function directStructDeps(
127
+ struct: CollectedStruct,
128
+ knownStructNames: Set<string>,
129
+ ): string[] {
130
+ const deps = new Set<string>();
131
+ for (const member of struct.members) {
132
+ if (member.type === undefined) {
133
+ continue;
134
+ }
135
+
136
+ // Strip array suffixes: `Foo[]`, `Foo[3]`, `Foo[3][2]` → `Foo`.
137
+ const base = member.type.split("[")[0];
138
+
139
+ if (knownStructNames.has(base) && base !== struct.name) {
140
+ deps.add(base);
141
+ }
142
+ }
143
+
144
+ return [...deps];
145
+ }
146
+
147
+ /**
148
+ * Walks the dep graph from `root` and returns the set of all transitively
149
+ * referenced struct names (excluding the root itself).
150
+ */
151
+ function transitiveDeps(
152
+ root: CollectedStruct,
153
+ byName: Map<string, CollectedStruct>,
154
+ ): string[] {
155
+ const visited = new Set<string>();
156
+ const knownNames = new Set(byName.keys());
157
+ const stack = directStructDeps(root, knownNames);
158
+
159
+ while (stack.length > 0) {
160
+ const next = stack.pop();
161
+
162
+ if (next === undefined || visited.has(next) || next === root.name) {
163
+ continue;
164
+ }
165
+
166
+ visited.add(next);
167
+
168
+ const def = byName.get(next);
169
+
170
+ if (def !== undefined) {
171
+ stack.push(...directStructDeps(def, knownNames));
172
+ }
173
+ }
174
+
175
+ return [...visited];
176
+ }
177
+
178
+ /**
179
+ * Builds a name → definition map from collected structs, throwing
180
+ * `EIP712_DUPLICATE_STRUCT_NAME` when two structs share a name but have
181
+ * different member lists. Definitions with identical members are silently
182
+ * deduplicated (same struct seen across multiple build infos / partial
183
+ * recompiles).
184
+ *
185
+ * The comparison uses the full member list — including members whose type
186
+ * isn't EIP-712 encodable (mappings, function types) — so that two structs
187
+ * differing only in unsupported members are detected as conflicting rather
188
+ * than collapsed into one. Comparing only the encoded head would let a
189
+ * non decodable definition silently win over an encodable one, dropping the
190
+ * struct from the canonical output.
191
+ *
192
+ * Conflicts on a name reachable from any selected struct (selected roots plus
193
+ * their transitive deps) throw, since the selected struct's inlined dep head
194
+ * would otherwise depend on which conflicting copy happened to be seen first.
195
+ * Conflicts on names truly unreachable from the selected set are silently
196
+ * deduped (first wins). Selected structs are processed first so they win over
197
+ * non-selected copies.
198
+ */
199
+ function indexByName(
200
+ structs: CollectedStruct[],
201
+ selectedNames: Set<string>,
202
+ ): Map<string, CollectedStruct> {
203
+ const byName = new Map<string, CollectedStruct>();
204
+ const fingerprintByName = new Map<string, string>();
205
+ const sourceByName = new Map<string, string>();
206
+ const deferredConflicts = new Map<
207
+ string,
208
+ { firstSource: string; secondSource: string }
209
+ >();
210
+
211
+ const ordered = [
212
+ ...structs.filter((s) => selectedNames.has(s.name)),
213
+ ...structs.filter((s) => !selectedNames.has(s.name)),
214
+ ];
215
+
216
+ for (const struct of ordered) {
217
+ const fingerprint = fingerprintStruct(struct);
218
+ const existingFingerprint = fingerprintByName.get(struct.name);
219
+
220
+ if (existingFingerprint === undefined) {
221
+ byName.set(struct.name, struct);
222
+ fingerprintByName.set(struct.name, fingerprint);
223
+ sourceByName.set(struct.name, struct.sourcePath);
224
+ continue;
225
+ }
226
+
227
+ if (existingFingerprint === fingerprint) {
228
+ continue;
229
+ }
230
+
231
+ if (!selectedNames.has(struct.name)) {
232
+ if (!deferredConflicts.has(struct.name)) {
233
+ deferredConflicts.set(struct.name, {
234
+ firstSource: sourceByName.get(struct.name) ?? "<unknown>",
235
+ secondSource: struct.sourcePath,
236
+ });
237
+ }
238
+ continue;
239
+ }
240
+
241
+ throw new HardhatError(
242
+ HardhatError.ERRORS.CORE.SOLIDITY_TESTS.EIP712_DUPLICATE_STRUCT_NAME,
243
+ {
244
+ name: struct.name,
245
+ firstSource: sourceByName.get(struct.name) ?? "<unknown>",
246
+ secondSource: struct.sourcePath,
247
+ },
248
+ );
249
+ }
250
+
251
+ if (deferredConflicts.size > 0) {
252
+ const reachable = reachableFromSelected(byName, selectedNames);
253
+ for (const [name, sources] of deferredConflicts) {
254
+ if (reachable.has(name)) {
255
+ throw new HardhatError(
256
+ HardhatError.ERRORS.CORE.SOLIDITY_TESTS.EIP712_DUPLICATE_STRUCT_NAME,
257
+ { name, ...sources },
258
+ );
259
+ }
260
+ }
261
+ }
262
+
263
+ return byName;
264
+ }
265
+
266
+ /**
267
+ * Set of struct names transitively referenced by any struct in `selectedNames`.
268
+ * The walk uses whatever first-wins definition is in `byName`; that's enough
269
+ * for conflict detection since we only need to know whether a name is
270
+ * reachable, not which conflicting copy is the "right" one.
271
+ */
272
+ function reachableFromSelected(
273
+ byName: Map<string, CollectedStruct>,
274
+ selectedNames: Set<string>,
275
+ ): Set<string> {
276
+ const knownNames = new Set(byName.keys());
277
+ const reachable = new Set<string>();
278
+ const stack: string[] = [];
279
+
280
+ for (const name of selectedNames) {
281
+ const root = byName.get(name);
282
+ if (root !== undefined) {
283
+ stack.push(...directStructDeps(root, knownNames));
284
+ }
285
+ }
286
+
287
+ while (stack.length > 0) {
288
+ const next = stack.pop();
289
+ if (next === undefined || reachable.has(next)) {
290
+ continue;
291
+ }
292
+
293
+ reachable.add(next);
294
+
295
+ const def = byName.get(next);
296
+ if (def !== undefined) {
297
+ stack.push(...directStructDeps(def, knownNames));
298
+ }
299
+ }
300
+
301
+ return reachable;
302
+ }
303
+
304
+ /**
305
+ * A stable string capturing every member of `struct`, used to detect
306
+ * conflicting definitions of the same name. Unlike `encodeStructHead`, this
307
+ * preserves members whose type can't be EIP-712 encoded — they're emitted with
308
+ * an `<unsupported>` sentinel — so structs that differ only in mapping or
309
+ * function-type members aren't collapsed together.
310
+ */
311
+ function fingerprintStruct(struct: CollectedStruct): string {
312
+ const segments = struct.members.map(
313
+ (m) => `${m.type ?? "<unsupported>"} ${m.name}`,
314
+ );
315
+
316
+ return `${struct.name}(${segments.join(",")})`;
317
+ }
@@ -0,0 +1,225 @@
1
+ // The same patterns are reused for every source file, so compile each one once.
2
+ const compiledGlobCache = new Map<string, RegExp>();
3
+
4
+ /**
5
+ * Returns true when `path` should be included given user-supplied include and
6
+ * exclude glob lists. `include` is the gate: an empty `include` matches
7
+ * nothing. `exclude` then narrows the included set.
8
+ */
9
+ export function isPathSelected(
10
+ path: string,
11
+ include: string[],
12
+ exclude: string[],
13
+ ): boolean {
14
+ if (include.length === 0) {
15
+ return false;
16
+ }
17
+
18
+ if (!matchesAny(path, include)) {
19
+ return false;
20
+ }
21
+
22
+ if (exclude.length > 0 && matchesAny(path, exclude)) {
23
+ return false;
24
+ }
25
+
26
+ return true;
27
+ }
28
+
29
+ /**
30
+ * Returns true if `value` matches at least one of the given glob patterns.
31
+ */
32
+ function matchesAny(value: string, patterns: string[]): boolean {
33
+ for (const pattern of patterns) {
34
+ if (getCompiledGlob(pattern).test(value)) {
35
+ return true;
36
+ }
37
+ }
38
+
39
+ return false;
40
+ }
41
+
42
+ /**
43
+ * Compiles a glob pattern into a regular expression. Supports `*`, `**`,
44
+ * `?`, `[abc]` (including `[a-z]` ranges and `[!abc]` / `[^abc]` negation),
45
+ * and `{a,b,c}` alternation.
46
+ */
47
+ function globToRegExp(pattern: string): RegExp {
48
+ return new RegExp(`^${translateGlob(pattern)}$`);
49
+ }
50
+
51
+ function translateGlob(pattern: string): string {
52
+ let regex = "";
53
+ let i = 0;
54
+ while (i < pattern.length) {
55
+ const c = pattern[i];
56
+
57
+ if (c === "*") {
58
+ if (pattern[i + 1] === "*") {
59
+ // `**` as a full path segment matches zero or more directories,
60
+ // e.g. `**/x.sol` matches both `x.sol` and `a/b/x.sol`.
61
+ const afterSlash = i === 0 || pattern[i - 1] === "/";
62
+ const beforeSlash = pattern[i + 2] === "/";
63
+ if (afterSlash && beforeSlash) {
64
+ regex += "(?:.*/)?";
65
+ i += 3;
66
+ } else {
67
+ regex += ".*";
68
+ i += 2;
69
+ }
70
+ } else {
71
+ regex += "[^/]*";
72
+ i += 1;
73
+ }
74
+ } else if (c === "?") {
75
+ regex += "[^/]";
76
+ i += 1;
77
+ } else if (c === "[") {
78
+ const end = findCharClassEnd(pattern, i);
79
+ if (end !== -1) {
80
+ regex += translateCharClass(pattern.slice(i + 1, end));
81
+ i = end + 1;
82
+ continue;
83
+ }
84
+
85
+ regex += "\\[";
86
+ i += 1;
87
+ } else if (c === "{") {
88
+ const end = findBraceEnd(pattern, i);
89
+ if (end !== -1) {
90
+ const alternatives = splitBraceAlternatives(
91
+ pattern.slice(i + 1, end),
92
+ ).map(translateGlob);
93
+
94
+ regex += `(?:${alternatives.join("|")})`;
95
+ i = end + 1;
96
+
97
+ continue;
98
+ }
99
+
100
+ regex += "\\{";
101
+ i += 1;
102
+ } else if (/[.+^$()|\\\]}]/.test(c)) {
103
+ regex += `\\${c}`;
104
+ i += 1;
105
+ } else {
106
+ regex += c;
107
+ i += 1;
108
+ }
109
+ }
110
+
111
+ return regex;
112
+ }
113
+
114
+ /**
115
+ * Returns the index of the `]` that closes the character class opened at
116
+ * `start`, or -1 if none is found. E.g. for `a[bc]d` starting at 1, returns 4.
117
+ */
118
+ function findCharClassEnd(pattern: string, start: number): number {
119
+ for (let i = start + 1; i < pattern.length; i++) {
120
+ if (pattern[i] === "]") {
121
+ return i;
122
+ }
123
+ }
124
+
125
+ return -1;
126
+ }
127
+
128
+ /**
129
+ * Returns the index of the `}` that closes the brace group opened at `start`,
130
+ * handling nested groups and skipping character classes, or -1 if unterminated.
131
+ * E.g. `{a,{b,c}}` returns the outer `}`; `{a[}]b}` skips the bracketed `}`.
132
+ */
133
+ function findBraceEnd(pattern: string, start: number): number {
134
+ let depth = 1;
135
+ let i = start + 1;
136
+ while (i < pattern.length) {
137
+ const c = pattern[i];
138
+ if (c === "{") {
139
+ depth += 1;
140
+ } else if (c === "}") {
141
+ depth -= 1;
142
+ if (depth === 0) {
143
+ return i;
144
+ }
145
+ } else if (c === "[") {
146
+ const end = findCharClassEnd(pattern, i);
147
+ if (end !== -1) {
148
+ i = end;
149
+ }
150
+ }
151
+
152
+ i += 1;
153
+ }
154
+
155
+ return -1;
156
+ }
157
+
158
+ /**
159
+ * Splits a brace group's body on top-level commas, leaving commas inside
160
+ * nested braces or character classes untouched. E.g. `a,{b,c},[d,e]` →
161
+ * `["a", "{b,c}", "[d,e]"]`.
162
+ */
163
+ function splitBraceAlternatives(inside: string): string[] {
164
+ const result: string[] = [];
165
+ let depth = 0;
166
+ let start = 0;
167
+ let i = 0;
168
+ while (i < inside.length) {
169
+ const c = inside[i];
170
+
171
+ if (c === "{") {
172
+ depth += 1;
173
+ } else if (c === "}") {
174
+ depth -= 1;
175
+ } else if (c === "[") {
176
+ const end = findCharClassEnd(inside, i);
177
+ if (end !== -1) {
178
+ i = end;
179
+ }
180
+ } else if (c === "," && depth === 0) {
181
+ result.push(inside.slice(start, i));
182
+ start = i + 1;
183
+ }
184
+
185
+ i += 1;
186
+ }
187
+
188
+ result.push(inside.slice(start));
189
+ return result;
190
+ }
191
+
192
+ /**
193
+ * Translates a glob character class body into a regex character class.
194
+ * E.g. `Mm` → `[Mm]`, `!ab` → `[^ab]`, `a-z` → `[a-z]`.
195
+ */
196
+ function translateCharClass(content: string): string {
197
+ let negated = false;
198
+ let body = content;
199
+ if (body.startsWith("!") || body.startsWith("^")) {
200
+ negated = true;
201
+ body = body.slice(1);
202
+ }
203
+
204
+ let escaped = "";
205
+ for (const ch of body) {
206
+ if (ch === "\\" || ch === "]") {
207
+ escaped += `\\${ch}`;
208
+ } else {
209
+ escaped += ch;
210
+ }
211
+ }
212
+
213
+ return `(?!/)[${negated ? "^" : ""}${escaped}]`;
214
+ }
215
+
216
+ function getCompiledGlob(pattern: string): RegExp {
217
+ let compiled = compiledGlobCache.get(pattern);
218
+
219
+ if (compiled === undefined) {
220
+ compiled = globToRegExp(pattern);
221
+ compiledGlobCache.set(pattern, compiled);
222
+ }
223
+
224
+ return compiled;
225
+ }
@@ -0,0 +1,120 @@
1
+ import type { CollectedStruct } from "./ast-walker.js";
2
+ import type { SolidityBuildInfoOutput } from "../../../../types/solidity/solidity-artifacts.js";
3
+ import type { BuildInfoAndOutput } from "../edr-artifacts.js";
4
+
5
+ import {
6
+ bytesIncludesUtf8String,
7
+ bytesToUtf8String,
8
+ } from "@nomicfoundation/hardhat-utils/bytes";
9
+
10
+ import { HARDHAT_PROJECT_INPUT_SOURCE_NAME_ROOT } from "../../solidity/constants.js";
11
+
12
+ import {
13
+ buildUserDefinedValueTypeIndex,
14
+ extractStructsFromAst,
15
+ } from "./ast-walker.js";
16
+ import { canonicalizeStructs } from "./canonicalize.js";
17
+ import { isPathSelected } from "./glob.js";
18
+
19
+ export interface Eip712TypesConfig {
20
+ include: string[];
21
+ exclude: string[];
22
+ }
23
+
24
+ // When a transitive project file doesn't produce an artifact — and so is
25
+ // missing from `inputToUserSource` — stripping this prefix recovers the
26
+ // user-facing path that the user's include/exclude globs are written against.
27
+ const PROJECT_INPUT_SOURCE_NAME_PREFIX = `${HARDHAT_PROJECT_INPUT_SOURCE_NAME_ROOT}/`;
28
+
29
+ /**
30
+ * Walks every compiled source's AST, extracts every struct definition, and
31
+ * returns the flat list of canonical EIP-712 type strings expected by EDR's
32
+ * `eip712CanonicalTypes` config field. Only structs from sources matching
33
+ * `include`/`exclude` are emitted; non-selected sources still feed the dep
34
+ * graph so cross-file deps inline correctly.
35
+ *
36
+ * `inputToUserSource` maps solc input source names to user source names; it's
37
+ * built by the caller from the artifact set so we don't pay to parse every
38
+ * build info just to recover that mapping.
39
+ *
40
+ * When `include` is empty/unset the feature is off: collection short-circuits
41
+ * and returns an empty list without parsing any build info.
42
+ */
43
+ export function collectEip712CanonicalTypes(
44
+ buildInfosAndOutputs: BuildInfoAndOutput[],
45
+ inputToUserSource: ReadonlyMap<string, string>,
46
+ config: Eip712TypesConfig,
47
+ ): string[] {
48
+ const { include, exclude } = config;
49
+
50
+ if (include.length === 0) {
51
+ return [];
52
+ }
53
+
54
+ const collected: CollectedStruct[] = [];
55
+ const selectedNames = new Set<string>();
56
+
57
+ for (const { buildInfo, output } of buildInfosAndOutputs) {
58
+ // Byte-level fast path: a build info whose source bytes don't contain
59
+ // `struct ` can't define any EIP-712 type, so skip JSON-parsing its output.
60
+ if (!bytesIncludesUtf8String(buildInfo, "struct ")) {
61
+ continue;
62
+ }
63
+
64
+ const parsedOutput: SolidityBuildInfoOutput = JSON.parse(
65
+ bytesToUtf8String(output),
66
+ );
67
+
68
+ const sources = parsedOutput.output.sources;
69
+ if (sources === undefined) {
70
+ continue;
71
+ }
72
+
73
+ // Two constraints determine the index's scope:
74
+ //
75
+ // 1. Per build info, not pooled across them. solc assigns node ids
76
+ // fresh in each compilation, so the same numeric id can mean
77
+ // different user-defined value types in different builds. Pooling
78
+ // would let one compilation's definition silently overwrite
79
+ // another's at the same key, mis-resolving `referencedDeclaration`.
80
+ // See the test "scopes user-defined value type resolution per build
81
+ // info when node ids collide" for a repro.
82
+ //
83
+ // 2. Whole build info, not narrowed to a subset of sources. A struct
84
+ // member's `referencedDeclaration` can point at a user-defined
85
+ // value type defined in any source within the same compilation, so
86
+ // the index must cover every source in the build.
87
+ const userDefinedValueTypeI = buildUserDefinedValueTypeIndex(
88
+ Object.values(sources).map((s) => s.ast),
89
+ );
90
+
91
+ for (const [inputSourceName, source] of Object.entries(sources)) {
92
+ let userSourceName = inputToUserSource.get(inputSourceName);
93
+
94
+ if (userSourceName === undefined) {
95
+ userSourceName = inputSourceName.startsWith(
96
+ PROJECT_INPUT_SOURCE_NAME_PREFIX,
97
+ )
98
+ ? inputSourceName.slice(PROJECT_INPUT_SOURCE_NAME_PREFIX.length)
99
+ : inputSourceName;
100
+ }
101
+
102
+ // Collect every source so non-selected files can serve as dep targets;
103
+ // selection is enforced at emit time via `selectedNames`.
104
+ const structs = extractStructsFromAst(
105
+ source.ast,
106
+ userSourceName,
107
+ userDefinedValueTypeI,
108
+ );
109
+ collected.push(...structs);
110
+
111
+ if (isPathSelected(userSourceName, include, exclude)) {
112
+ for (const s of structs) {
113
+ selectedNames.add(s.name);
114
+ }
115
+ }
116
+ }
117
+ }
118
+
119
+ return canonicalizeStructs(collected, selectedNames);
120
+ }