hardhat 3.1.2 → 3.1.4

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 (42) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/src/internal/builtin-plugins/coverage/coverage-manager.d.ts +14 -14
  3. package/dist/src/internal/builtin-plugins/coverage/coverage-manager.d.ts.map +1 -1
  4. package/dist/src/internal/builtin-plugins/coverage/coverage-manager.js +66 -110
  5. package/dist/src/internal/builtin-plugins/coverage/coverage-manager.js.map +1 -1
  6. package/dist/src/internal/builtin-plugins/coverage/hook-handlers/solidity.d.ts.map +1 -1
  7. package/dist/src/internal/builtin-plugins/coverage/hook-handlers/solidity.js +13 -6
  8. package/dist/src/internal/builtin-plugins/coverage/hook-handlers/solidity.js.map +1 -1
  9. package/dist/src/internal/builtin-plugins/coverage/process-coverage.d.ts +21 -0
  10. package/dist/src/internal/builtin-plugins/coverage/process-coverage.d.ts.map +1 -0
  11. package/dist/src/internal/builtin-plugins/coverage/process-coverage.js +291 -0
  12. package/dist/src/internal/builtin-plugins/coverage/process-coverage.js.map +1 -0
  13. package/dist/src/internal/builtin-plugins/coverage/reports/html.d.ts +3 -0
  14. package/dist/src/internal/builtin-plugins/coverage/reports/html.d.ts.map +1 -0
  15. package/dist/src/internal/builtin-plugins/coverage/reports/html.js +37 -0
  16. package/dist/src/internal/builtin-plugins/coverage/reports/html.js.map +1 -0
  17. package/dist/src/internal/builtin-plugins/coverage/types.d.ts +9 -3
  18. package/dist/src/internal/builtin-plugins/coverage/types.d.ts.map +1 -1
  19. package/dist/src/internal/builtin-plugins/network-manager/config-resolution.js +1 -1
  20. package/dist/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.d.ts +1 -1
  21. package/dist/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.d.ts.map +1 -1
  22. package/dist/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.js +26 -25
  23. package/dist/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.js.map +1 -1
  24. package/dist/src/internal/builtin-plugins/solidity/build-system/solc-info.d.ts.map +1 -1
  25. package/dist/src/internal/builtin-plugins/solidity/build-system/solc-info.js +3 -0
  26. package/dist/src/internal/builtin-plugins/solidity/build-system/solc-info.js.map +1 -1
  27. package/dist/src/internal/builtin-plugins/test/task-action.d.ts.map +1 -1
  28. package/dist/src/internal/builtin-plugins/test/task-action.js +53 -53
  29. package/dist/src/internal/builtin-plugins/test/task-action.js.map +1 -1
  30. package/package.json +3 -2
  31. package/src/internal/builtin-plugins/coverage/coverage-manager.ts +121 -176
  32. package/src/internal/builtin-plugins/coverage/hook-handlers/solidity.ts +19 -8
  33. package/src/internal/builtin-plugins/coverage/process-coverage.ts +442 -0
  34. package/src/internal/builtin-plugins/coverage/reports/html.ts +56 -0
  35. package/src/internal/builtin-plugins/coverage/types.ts +8 -3
  36. package/src/internal/builtin-plugins/network-manager/config-resolution.ts +1 -1
  37. package/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.ts +40 -26
  38. package/src/internal/builtin-plugins/solidity/build-system/solc-info.ts +3 -0
  39. package/src/internal/builtin-plugins/test/task-action.ts +63 -66
  40. package/templates/hardhat-3/01-node-test-runner-viem/package.json +1 -1
  41. package/templates/hardhat-3/02-mocha-ethers/package.json +4 -4
  42. package/templates/hardhat-3/03-minimal/package.json +1 -1
@@ -3,14 +3,19 @@ import type { CoverageMetadata } from "../types.js";
3
3
 
4
4
  import path from "node:path";
5
5
 
6
- import { addStatementCoverageInstrumentation } from "@nomicfoundation/edr";
6
+ import {
7
+ addStatementCoverageInstrumentation,
8
+ latestSupportedSolidityVersion,
9
+ } from "@nomicfoundation/edr";
7
10
  import {
8
11
  assertHardhatInvariant,
9
12
  HardhatError,
10
13
  } from "@nomicfoundation/hardhat-errors";
11
14
  import { ensureError } from "@nomicfoundation/hardhat-utils/error";
12
15
  import { readUtf8File } from "@nomicfoundation/hardhat-utils/fs";
16
+ import { findClosestPackageRoot } from "@nomicfoundation/hardhat-utils/package";
13
17
  import debug from "debug";
18
+ import { satisfies } from "semver";
14
19
 
15
20
  import { CoverageManagerImplementation } from "../coverage-manager.js";
16
21
 
@@ -35,6 +40,13 @@ export default async (): Promise<Partial<SolidityHooks>> => ({
35
40
 
36
41
  if (context.globalOptions.coverage && !isTestSource) {
37
42
  try {
43
+ const latestSupportedVersion = latestSupportedSolidityVersion();
44
+ if (!satisfies(solcVersion, `<=${latestSupportedVersion}`)) {
45
+ console.log(
46
+ `Solidity version ${solcVersion} is not yet supported for coverage instrumentation. Hardhat will try the latest supported version ${latestSupportedVersion} instead.`,
47
+ );
48
+ solcVersion = latestSupportedVersion;
49
+ }
38
50
  const { source, metadata } = addStatementCoverageInstrumentation(
39
51
  fileContent,
40
52
  sourceName,
@@ -63,13 +75,11 @@ export default async (): Promise<Partial<SolidityHooks>> => ({
63
75
  fsPath,
64
76
  );
65
77
  const tag = Buffer.from(m.tag).toString("hex");
66
- const startLine = lineNumbers[m.startUtf16];
67
- const endLine = lineNumbers[m.endUtf16 - 1];
68
78
  coverageMetadata.push({
69
79
  relativePath,
70
80
  tag,
71
- startLine,
72
- endLine,
81
+ startUtf16: m.startUtf16,
82
+ endUtf16: m.endUtf16,
73
83
  });
74
84
  break;
75
85
  default:
@@ -124,9 +134,10 @@ export default async (): Promise<Partial<SolidityHooks>> => ({
124
134
  // NOTE: We add the coverage.sol straight into sources here. The alternative
125
135
  // would be to do it during the resolution phase. However, we decided this
126
136
  // is a simpler solution, at least for now.
127
- const content = await readUtf8File(
128
- path.join(import.meta.dirname, "../../../../../../coverage.sol"),
129
- );
137
+ const packageRoot = await findClosestPackageRoot(import.meta.url);
138
+ const coverageSolPath = path.join(packageRoot, "coverage.sol");
139
+
140
+ const content = await readUtf8File(coverageSolPath);
130
141
  solcInput.sources[COVERAGE_LIBRARY_PATH] = { content };
131
142
  }
132
143
 
@@ -0,0 +1,442 @@
1
+ import type {
2
+ CoverageMetadata,
3
+ ReportCoverageStatement,
4
+ Statement,
5
+ } from "./types.js";
6
+
7
+ import chalk from "chalk";
8
+
9
+ // Use constants for the Uint8Array to improve memory usage (1 byte vs 8 bytes per item)
10
+ const STATUS_NOT_EXECUTED = 0; // equivalent to false
11
+ const STATUS_EXECUTED = 1; // equivalent to true
12
+ const STATUS_IGNORED = 2; // equivalent to null
13
+
14
+ /**
15
+ * Processes the raw EDR coverage information for a file and returns the executed and
16
+ * non-executed statements and lines.
17
+ *
18
+ * @param fileContent The original file content being analyzed
19
+ * @param metadata Coverage metadata received from EDR for this file
20
+ * @param hitTags The coverage tags recorded as executed during the test run
21
+ * for this specific file.
22
+ *
23
+ * @returns An object containing:
24
+ * - statements: the executed and not-executed statements
25
+ * - lines: the executed and not-executed line numbers
26
+ */
27
+ export function getProcessedCoverageInfo(
28
+ fileContent: string,
29
+ metadata: CoverageMetadata,
30
+ hitTags: Set<string>,
31
+ ): {
32
+ lines: {
33
+ executed: Map<number, string>;
34
+ unexecuted: Map<number, string>;
35
+ };
36
+ } {
37
+ const statementsByExecution = partitionStatementsByExecution(
38
+ metadata,
39
+ hitTags,
40
+ );
41
+
42
+ const { start, end } = getCoverageBounds(
43
+ statementsByExecution,
44
+ fileContent.length,
45
+ );
46
+
47
+ const characterCoverage = buildCharacterCoverage(
48
+ fileContent,
49
+ start,
50
+ end,
51
+ statementsByExecution.unexecuted,
52
+ );
53
+
54
+ // printStatementsForDebugging(fileContent, statementsByExecution);
55
+ // printCharacterCoverageForDebugging(fileContent, characterCoverage);
56
+
57
+ return {
58
+ lines: partitionLinesByExecution(fileContent, characterCoverage),
59
+ };
60
+ }
61
+
62
+ function partitionStatementsByExecution(
63
+ metadata: CoverageMetadata,
64
+ hitTags: Set<string>,
65
+ ): {
66
+ executed: Statement[];
67
+ unexecuted: Statement[];
68
+ } {
69
+ const executed: Statement[] = [];
70
+ const unexecuted: Statement[] = [];
71
+
72
+ for (const node of metadata) {
73
+ if (hitTags.has(node.tag)) {
74
+ executed.push(node);
75
+ } else {
76
+ unexecuted.push(node);
77
+ }
78
+ }
79
+
80
+ return {
81
+ executed,
82
+ unexecuted,
83
+ };
84
+ }
85
+
86
+ // Determine the minimum and maximum character indexes that define the range
87
+ // where coverage should be calculated, excluding parts that the tests never
88
+ // reach. This removes irrelevant sections from coverage analysis, such as the
89
+ // beginning or end of each Solidity file.
90
+ // Example:
91
+ // // SPDX-License-Identifier: MIT
92
+ // pragma solidity ^0.8.0;
93
+ function getCoverageBounds(
94
+ statementsByExecution: {
95
+ executed: Statement[];
96
+ unexecuted: Statement[];
97
+ },
98
+ fileLength: number,
99
+ ): { start: number; end: number } {
100
+ let start = fileLength;
101
+ let end = 0;
102
+
103
+ const { executed, unexecuted } = statementsByExecution;
104
+
105
+ for (const s of [...executed, ...unexecuted]) {
106
+ if (s.startUtf16 < start) {
107
+ start = s.startUtf16;
108
+ }
109
+
110
+ if (s.endUtf16 > end) {
111
+ end = s.endUtf16;
112
+ }
113
+ }
114
+
115
+ return { start, end };
116
+ }
117
+
118
+ // Return an array with the same length as the file content. Each position in
119
+ // the array corresponds to a character in the file. The value at each position
120
+ // indicates whether that character was executed during tests (STATUS_EXECUTED),
121
+ // not executed (STATUS_NOT_EXECUTED), or not relevant for coverage
122
+ // (STATUS_IGNORED). STATUS_IGNORED is used to indicate characters that are not
123
+ // executed during test, such as those found at the start or end of every Solidity file.
124
+ // Example:
125
+ // // SPDX-License-Identifier: MIT
126
+ // pragma solidity ^0.8.0;
127
+ function buildCharacterCoverage(
128
+ fileContent: string,
129
+ start: number,
130
+ end: number,
131
+ unexecutedStatements: Statement[],
132
+ ): Uint8Array {
133
+ // Use Uint8Array instead of Array<null | boolean> for memory efficiency.
134
+ // We initialize with STATUS_IGNORED (equivalent to filling with null)
135
+ const characterCoverage = new Uint8Array(fileContent.length).fill(
136
+ STATUS_IGNORED,
137
+ );
138
+
139
+ // Initially mark all characters that may be executed during the tests as
140
+ // STATUS_EXECUTED. They will be set to false later if they are not executed.
141
+ // The coverage statement received from EDR will provide this information.
142
+ // Setting everything to true first simplifies the logic for extracting
143
+ // coverage data. Starting with false and toggling only covered characters
144
+ // would make statement processing more complex.
145
+ for (let i = start; i < end; i++) {
146
+ if (charMustBeIgnored(fileContent[i])) {
147
+ continue;
148
+ }
149
+
150
+ characterCoverage[i] = STATUS_EXECUTED;
151
+ }
152
+
153
+ for (const statement of unexecutedStatements) {
154
+ for (let i = statement.startUtf16; i < statement.endUtf16; i++) {
155
+ if (characterCoverage[i] === STATUS_IGNORED) {
156
+ continue;
157
+ }
158
+
159
+ characterCoverage[i] = STATUS_NOT_EXECUTED;
160
+ }
161
+ }
162
+
163
+ markNonExecutablePatterns(fileContent, characterCoverage);
164
+
165
+ return characterCoverage;
166
+ }
167
+
168
+ // The following characters are not relevant for the code coverage, so they
169
+ // must be ignored when marking characters as executed (true) or not executed (false)
170
+ function charMustBeIgnored(char: string): boolean {
171
+ const code = char.charCodeAt(0);
172
+
173
+ return (
174
+ code === 32 || // Space
175
+ (code >= 9 && code <= 13) || // \t (9), \n (10), \v (11), \f (12), \r (13)
176
+ code === 123 || // {
177
+ code === 125 // }
178
+ );
179
+ }
180
+
181
+ // Mark different types of substrings as not relevant for code coverage (set them to STATUS_IGNORED).
182
+ // For example, all "else" substrings or comments are not relevant for code coverage.
183
+ function markNonExecutablePatterns(
184
+ fileContent: string,
185
+ characterCoverage: Uint8Array,
186
+ ) {
187
+ // Comments that start with //
188
+ markMatchingCharsWithIgnored(
189
+ fileContent,
190
+ characterCoverage,
191
+ /\/\/.*?(?=\n|$)/g,
192
+ );
193
+
194
+ // Comments wrapped between /* and */
195
+ markMatchingCharsWithIgnored(
196
+ fileContent,
197
+ characterCoverage,
198
+ /\/\*[\s\S]*?\*\//g,
199
+ );
200
+
201
+ // Lines containing `else`, since they do not represent executable code by themselves.
202
+ // Keep in mind that `else` is preceded by a closing brace and followed by an opening brace.
203
+ // This can span multiple lines.
204
+ markMatchingCharsWithIgnored(
205
+ fileContent,
206
+ characterCoverage,
207
+ /\}\s*\belse\b\s*\{/g,
208
+ );
209
+
210
+ // Lines containing the function signature. This can span multiple lines
211
+ markMatchingCharsWithIgnored(
212
+ fileContent,
213
+ characterCoverage,
214
+ /^\s*(function\s+[A-Za-z_$][A-Za-z0-9_$]*\s*\([\s\S]*?\)[^{]*?)(?=\s*\{)/gm,
215
+ );
216
+
217
+ // Lines containing the catch signature. This can span multiple lines.
218
+ markMatchingCharsWithIgnored(
219
+ fileContent,
220
+ characterCoverage,
221
+ /\bcatch\b(?:\s+[A-Za-z_][A-Za-z0-9_]*)?\s*(?:\([\s\S]*?\))?(?=\s*\{)/g,
222
+ );
223
+ }
224
+
225
+ function markMatchingCharsWithIgnored(
226
+ fileContent: string,
227
+ characterCoverage: Uint8Array,
228
+ regex: RegExp,
229
+ ) {
230
+ for (const match of fileContent.matchAll(regex)) {
231
+ for (let i = match.index; i < match.index + match[0].length; i++) {
232
+ characterCoverage[i] = STATUS_IGNORED;
233
+ }
234
+ }
235
+ }
236
+
237
+ // Generate non overlapping coverage statements. Every character between the start
238
+ // and end indexes is guaranteed to be either executed or not executed.
239
+ // Unlike the statements received from EDR, which may include nested sub
240
+ // statements, these are processed and have no sub statements or overlapping statements.
241
+ // Example:
242
+ // [
243
+ // { start: 12, end: 20, executed: true },
244
+ // { start: 15, end: 17, executed: true }
245
+ // ]
246
+ // ->
247
+ // [
248
+ // { start: 12, end: 20, executed: true }
249
+ // ]
250
+ /* eslint-disable-next-line @typescript-eslint/no-unused-vars
251
+ -- currently not used, but it will be used to create more detailed.
252
+ It will be used to return statements from the function `getProcessedCoverageInfo` */
253
+ function getProcessedExecutedStatements(characterCoverage: Uint8Array): {
254
+ executed: ReportCoverageStatement[];
255
+ unexecuted: ReportCoverageStatement[];
256
+ } {
257
+ const executed = generateProcessedStatements(
258
+ characterCoverage,
259
+ STATUS_EXECUTED,
260
+ );
261
+ const unexecuted = generateProcessedStatements(
262
+ characterCoverage,
263
+ STATUS_NOT_EXECUTED,
264
+ );
265
+
266
+ return {
267
+ executed,
268
+ unexecuted,
269
+ };
270
+ }
271
+
272
+ // Based on the marked file, where each character is marked as either
273
+ // executed (STATUS_EXECUTED) or not executed (STATUS_NOT_EXECUTED),
274
+ // generate non-overlapping statements that indicate the start and
275
+ // end indices, along with whether they were executed or not.
276
+ function generateProcessedStatements(
277
+ characterCoverage: Uint8Array,
278
+ targetStatus: number,
279
+ ): ReportCoverageStatement[] {
280
+ const ranges: ReportCoverageStatement[] = [];
281
+ const fileLength = characterCoverage.length;
282
+
283
+ let start = -1;
284
+
285
+ for (let i = 0; i < fileLength; i++) {
286
+ if (characterCoverage[i] === targetStatus) {
287
+ if (start === -1) {
288
+ start = i; // begin new range
289
+ }
290
+ } else {
291
+ if (start !== -1) {
292
+ // Map back to boolean for the output object
293
+ ranges.push({
294
+ startUtf16: start,
295
+ endUtf16: i - 1,
296
+ executed: targetStatus === STATUS_EXECUTED,
297
+ });
298
+ start = -1;
299
+ }
300
+ }
301
+ }
302
+
303
+ // close last range if file ends inside a run
304
+ if (start !== -1) {
305
+ ranges.push({
306
+ startUtf16: start,
307
+ endUtf16: fileLength - 1,
308
+ executed: targetStatus === STATUS_EXECUTED,
309
+ });
310
+ }
311
+
312
+ return ranges;
313
+ }
314
+
315
+ // Return the executed and non executed lines based on the marked file.
316
+ // Some lines are excluded from the execution count, for example, comments.
317
+ function partitionLinesByExecution(
318
+ fileContent: string,
319
+ characterCoverage: Uint8Array,
320
+ ): {
321
+ executed: Map<number, string>;
322
+ unexecuted: Map<number, string>;
323
+ } {
324
+ const executed: Map<number, string> = new Map();
325
+ const unexecuted: Map<number, string> = new Map();
326
+
327
+ let lineStart = 0;
328
+ let isLineIgnored = true;
329
+ let isLineExecutedOrIgnored = true;
330
+ let lineNumber = 1; // File lines start at 1
331
+
332
+ // Helper to process a line and push to correct map
333
+ const processLine = (endIndex: number) => {
334
+ // Only process if the line isn't entirely ignored
335
+ if (!isLineIgnored) {
336
+ const lineText = fileContent.slice(lineStart, endIndex);
337
+
338
+ if (isLineExecutedOrIgnored) {
339
+ executed.set(lineNumber, lineText);
340
+ } else {
341
+ unexecuted.set(lineNumber, lineText);
342
+ }
343
+ }
344
+
345
+ // Reset state for the next line
346
+ lineStart = endIndex + 1;
347
+ isLineIgnored = true;
348
+ isLineExecutedOrIgnored = true;
349
+ lineNumber++;
350
+ };
351
+
352
+ for (let i = 0; i < fileContent.length; i++) {
353
+ const char = fileContent[i];
354
+ const status = characterCoverage[i];
355
+
356
+ if (status !== STATUS_IGNORED) {
357
+ isLineIgnored = false;
358
+ }
359
+ if (status === STATUS_NOT_EXECUTED) {
360
+ isLineExecutedOrIgnored = false;
361
+ }
362
+
363
+ if (char === "\n") {
364
+ processLine(i);
365
+ }
366
+ }
367
+
368
+ // Handle the final line if the file doesn't end in a newline
369
+ if (lineStart < fileContent.length) {
370
+ processLine(fileContent.length);
371
+ }
372
+
373
+ return { executed, unexecuted };
374
+ }
375
+
376
+ // Enable this function while debugging to display the coverage for a file.
377
+ // The file will be printed with green characters when they are executed,
378
+ // red characters when they are not executed,
379
+ // and gray characters when they are irrelevant for code coverage.
380
+ /* eslint-disable-next-line @typescript-eslint/no-unused-vars
381
+ -- this function can be enabled for debugging purposes */
382
+ function printStatementsForDebugging(
383
+ fileContent: string,
384
+ statementsByExecution: {
385
+ executed: Statement[];
386
+ unexecuted: Statement[];
387
+ },
388
+ ): void {
389
+ const relativePath =
390
+ statementsByExecution.executed.length > 0
391
+ ? statementsByExecution.executed[0].relativePath
392
+ : statementsByExecution.unexecuted[0].relativePath;
393
+
394
+ console.debug("Statements for file: " + relativePath);
395
+
396
+ console.debug("Executed statements:");
397
+ let counter = 0;
398
+ for (const statement of statementsByExecution.executed) {
399
+ console.debug(counter++ + " ---");
400
+
401
+ for (let i = statement.startUtf16; i < statement.endUtf16; i++) {
402
+ process.stdout.write(chalk.gray(fileContent[i]));
403
+ }
404
+
405
+ console.debug();
406
+ }
407
+
408
+ console.debug();
409
+
410
+ console.debug("Unexecuted statements:");
411
+ counter = 0;
412
+ for (const statement of statementsByExecution.unexecuted) {
413
+ console.debug(counter++ + " ---");
414
+
415
+ for (let i = statement.startUtf16; i < statement.endUtf16; i++) {
416
+ process.stdout.write(chalk.gray(fileContent[i]));
417
+ }
418
+
419
+ console.debug();
420
+ }
421
+ }
422
+
423
+ // Enable this function while debugging to display the coverage for a file.
424
+ // The file will be printed with green characters when they are executed,
425
+ // red characters when they are not executed,
426
+ // and gray characters when they are irrelevant for code coverage.
427
+ /* eslint-disable-next-line @typescript-eslint/no-unused-vars
428
+ -- this function can be enabled for debugging purposes */
429
+ function printCharacterCoverageForDebugging(
430
+ fileContent: string,
431
+ characterCoverage: Uint8Array,
432
+ ): void {
433
+ for (let i = 0; i < characterCoverage.length; i++) {
434
+ if (characterCoverage[i] === STATUS_IGNORED) {
435
+ process.stdout.write(chalk.gray(fileContent[i]));
436
+ } else if (characterCoverage[i] === STATUS_EXECUTED) {
437
+ process.stdout.write(chalk.green(fileContent[i]));
438
+ } else {
439
+ process.stdout.write(chalk.red(fileContent[i]));
440
+ }
441
+ }
442
+ }
@@ -0,0 +1,56 @@
1
+ import type { Report } from "../coverage-manager.js";
2
+ import type { FileCoverageData } from "@nomicfoundation/hardhat-vendored/coverage/types";
3
+
4
+ import path from "node:path";
5
+
6
+ import { mkdir } from "@nomicfoundation/hardhat-utils/fs";
7
+ import {
8
+ istanbulLibCoverage,
9
+ istanbulLibReport,
10
+ istanbulReports,
11
+ } from "@nomicfoundation/hardhat-vendored/coverage";
12
+
13
+ export async function generateHtmlReport(
14
+ report: Report,
15
+ htmlReportPath: string,
16
+ ): Promise<void> {
17
+ const baseDir = process.cwd();
18
+ const coverageMap = istanbulLibCoverage.createCoverageMap({});
19
+
20
+ await mkdir(htmlReportPath);
21
+
22
+ // Construct coverage data for each tested file,
23
+ // detailing whether each line was executed or not.
24
+ for (const [p, coverageInfo] of Object.entries(report)) {
25
+ const testedFilePath = path.join(baseDir, p);
26
+
27
+ const fc: FileCoverageData = {
28
+ path: testedFilePath,
29
+ statementMap: {},
30
+ fnMap: {}, // Cannot be derived from current report data
31
+ branchMap: {}, // Cannot be derived from current report data
32
+ s: {},
33
+ f: {}, // Cannot be derived from current report data
34
+ b: {},
35
+ };
36
+
37
+ for (const [line, count] of coverageInfo.lineExecutionCounts) {
38
+ fc.statementMap[line] = {
39
+ start: { line, column: 0 },
40
+ end: { line, column: 0 },
41
+ };
42
+
43
+ // TODO: currently EDR does not provide per-statement coverage counts
44
+ fc.s[line] = count > 0 ? 1 : 0; // mark as covered if hit at least once
45
+ }
46
+
47
+ coverageMap.addFileCoverage(fc);
48
+ }
49
+
50
+ const context = istanbulLibReport.createContext({
51
+ dir: htmlReportPath,
52
+ coverageMap,
53
+ });
54
+
55
+ istanbulReports.create("html", undefined).execute(context);
56
+ }
@@ -1,9 +1,14 @@
1
- export type Tag = string;
1
+ type Tag = string;
2
2
  export interface Statement {
3
3
  relativePath: string;
4
4
  tag: Tag;
5
- startLine: number;
6
- endLine: number;
5
+ startUtf16: number;
6
+ endUtf16: number;
7
+ }
8
+ export interface ReportCoverageStatement {
9
+ startUtf16: number;
10
+ endUtf16: number;
11
+ executed: boolean;
7
12
  }
8
13
  export type CoverageMetadata = Statement[];
9
14
  export type CoverageData = Tag[];
@@ -85,7 +85,7 @@ export function resolveEdrNetwork(
85
85
  networkConfig.allowBlocksWithSameTimestamp ?? false,
86
86
  allowUnlimitedContractSize:
87
87
  networkConfig.allowUnlimitedContractSize ?? false,
88
- blockGasLimit: BigInt(networkConfig.blockGasLimit ?? 30_000_000n),
88
+ blockGasLimit: BigInt(networkConfig.blockGasLimit ?? 60_000_000n),
89
89
  coinbase: resolveCoinbase(networkConfig.coinbase),
90
90
 
91
91
  forking: resolveForkingConfig(
@@ -40,7 +40,7 @@ const LINUX_ARM64_REPOSITORY_URL =
40
40
 
41
41
  export enum CompilerPlatform {
42
42
  LINUX = "linux-amd64",
43
- LINUX_ARM64 = "linux-aarch64",
43
+ LINUX_ARM64 = "linux-arm64",
44
44
  WINDOWS = "windows-amd64",
45
45
  MACOS = "macosx-amd64",
46
46
  WASM = "wasm",
@@ -48,6 +48,7 @@ export enum CompilerPlatform {
48
48
 
49
49
  interface CompilerBuild {
50
50
  path: string;
51
+ url?: string;
51
52
  version: string;
52
53
  longVersion: string;
53
54
  sha256: string;
@@ -341,46 +342,59 @@ export class CompilerDownloaderImplementation implements CompilerDownloader {
341
342
  }
342
343
  }
343
344
 
344
- return false;
345
+ // download the list in case the cached list contains older ARM64 Linux builds without URL
346
+ return list.builds
347
+ .map((b) => b.path.startsWith("solc-v") && b.url === undefined)
348
+ .reduce((a, b) => a || b, false);
345
349
  }
346
350
 
347
351
  async #downloadCompilerList(): Promise<void> {
348
352
  log(`Downloading compiler list for platform ${this.#platform}`);
349
- let url: string;
350
-
351
- if (this.#onLinuxArm()) {
352
- url = `${LINUX_ARM64_REPOSITORY_URL}/list.json`;
353
- } else {
354
- url = `${COMPILER_REPOSITORY_URL}/${this.#platform}/list.json`;
355
- }
356
353
  const downloadPath = this.#getCompilerListPath();
357
354
 
358
- await this.#downloadFunction(url, downloadPath);
355
+ // download hte official solc compiler list (now that ARM64 Linus is supported)
356
+ await this.#downloadFunction(
357
+ `${COMPILER_REPOSITORY_URL}/${this.#platform}/list.json`,
358
+ downloadPath,
359
+ );
360
+
361
+ // for Linux ARM64, we need to merge the official list with our custom builds
362
+ if (this.#platform === CompilerPlatform.LINUX_ARM64) {
363
+ // cache the official list since the file will be overwritten below
364
+ const officialCompilerList: CompilerList =
365
+ await readJsonFile(downloadPath);
366
+
367
+ await this.#downloadFunction(
368
+ `${LINUX_ARM64_REPOSITORY_URL}/list.json`,
369
+ downloadPath,
370
+ );
359
371
 
360
- // If using the arm64 binary mirror, the list.json file has different information than the solc official mirror, so we complete it
361
- if (this.#onLinuxArm()) {
362
- const compilerList: CompilerList = await readJsonFile(downloadPath);
363
- for (const build of compilerList.builds) {
372
+ // add missing information and an explicit URL for download
373
+ const armLinuxcompilerList: CompilerList =
374
+ await readJsonFile(downloadPath);
375
+ for (const build of armLinuxcompilerList.builds) {
364
376
  build.path = `solc-v${build.version}`;
377
+ build.url = LINUX_ARM64_REPOSITORY_URL;
365
378
  build.longVersion = build.version;
366
379
  }
367
380
 
368
- await writeJsonFile(downloadPath, compilerList);
369
- }
370
- }
381
+ // merge the official and custom lists
382
+ officialCompilerList.builds = officialCompilerList.builds.concat(
383
+ armLinuxcompilerList.builds,
384
+ );
385
+ officialCompilerList.releases = {
386
+ ...officialCompilerList.releases,
387
+ ...armLinuxcompilerList.releases,
388
+ };
371
389
 
372
- #onLinuxArm() {
373
- return this.#platform === CompilerPlatform.LINUX_ARM64;
390
+ await writeJsonFile(downloadPath, officialCompilerList);
391
+ }
374
392
  }
375
393
 
376
394
  async #downloadCompiler(build: CompilerBuild): Promise<string> {
377
- let url: string;
378
-
379
- if (this.#onLinuxArm()) {
380
- url = `${LINUX_ARM64_REPOSITORY_URL}/${build.path}`;
381
- } else {
382
- url = `${COMPILER_REPOSITORY_URL}/${this.#platform}/${build.path}`;
383
- }
395
+ // use the explicit URL if available or the default solc download URL if not
396
+ const defaultUrl = `${COMPILER_REPOSITORY_URL}/${this.#platform}`;
397
+ const url = `${build.url ?? defaultUrl}/${build.path}`;
384
398
 
385
399
  log(`Downloading compiler ${build.version} from ${url}`);
386
400