Skip to content

Commit a500780

Browse files
authored
Merge branch 'main' into 7253
2 parents ba6773d + af57fed commit a500780

File tree

24 files changed

+443
-96
lines changed

24 files changed

+443
-96
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@nomicfoundation/hardhat-viem-assertions": patch
3+
---
4+
5+
Support panic errors in `hardhat-viem-assetions` and improve error messages [7354](https://github.com/NomicFoundation/hardhat/pull/7354) [7384](https://github.com/NomicFoundation/hardhat/pull/7384)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@nomicfoundation/hardhat-mocha": patch
3+
"@nomicfoundation/hardhat-node-test-runner": patch
4+
"@nomicfoundation/hardhat-utils": patch
5+
"hardhat": patch
6+
---
7+
8+
Load resolved global options into environment variables during tests ([#7305](https://github.com/NomicFoundation/hardhat/pull/7305))

v-next/hardhat-mocha/src/task-action.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { MochaOptions } from "mocha";
55
import { resolve as pathResolve } from "node:path";
66

77
import { HardhatError } from "@nomicfoundation/hardhat-errors";
8+
import { setGlobalOptionsAsEnvVariables } from "@nomicfoundation/hardhat-utils/env";
89
import { getAllFilesMatching } from "@nomicfoundation/hardhat-utils/fs";
910
import {
1011
markTestRunDone,
@@ -53,6 +54,8 @@ const testWithHardhat: NewTaskActionFunction<TestActionArguments> = async (
5354
// Set an environment variable that plugins can use to detect when a process is running tests
5455
process.env.HH_TEST = "true";
5556

57+
setGlobalOptionsAsEnvVariables(hre.globalOptions);
58+
5659
if (!noCompile) {
5760
await hre.tasks.getTask("compile").run({});
5861
console.log();
@@ -71,10 +74,6 @@ const testWithHardhat: NewTaskActionFunction<TestActionArguments> = async (
7174
imports.push(tsx.href);
7275

7376
if (hre.globalOptions.coverage === true) {
74-
// NOTE: We set the HARDHAT_COVERAGE environment variable here because, as of now,
75-
// the global options are not automatically passed to the child processes.
76-
process.env.HARDHAT_COVERAGE = "true";
77-
7877
const coverage = new URL(
7978
import.meta.resolve("@nomicfoundation/hardhat-mocha/coverage"),
8079
);
@@ -83,10 +82,6 @@ const testWithHardhat: NewTaskActionFunction<TestActionArguments> = async (
8382
hre.config.test.mocha.require.push(coverage.href);
8483
}
8584

86-
if (hre.globalOptions.network !== undefined) {
87-
process.env.HARDHAT_NETWORK = hre.globalOptions.network;
88-
}
89-
9085
process.env.NODE_OPTIONS = imports
9186
.map((href) => `--import "${href}"`)
9287
.join(" ");

v-next/hardhat-node-test-runner/src/task-action.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { run } from "node:test";
77
import { URL } from "node:url";
88

99
import { hardhatTestReporter } from "@nomicfoundation/hardhat-node-test-reporter";
10+
import { setGlobalOptionsAsEnvVariables } from "@nomicfoundation/hardhat-utils/env";
1011
import { getAllFilesMatching } from "@nomicfoundation/hardhat-utils/fs";
1112
import { createNonClosingWriter } from "@nomicfoundation/hardhat-utils/stream";
1213
import { markTestRunStart, markTestRunDone } from "hardhat/internal/coverage";
@@ -59,6 +60,8 @@ const testWithHardhat: NewTaskActionFunction<TestActionArguments> = async (
5960
// Set an environment variable that plugins can use to detect when a process is running tests
6061
process.env.HH_TEST = "true";
6162

63+
setGlobalOptionsAsEnvVariables(hre.globalOptions);
64+
6265
if (!noCompile) {
6366
await hre.tasks.getTask("compile").run({});
6467
console.log();
@@ -76,20 +79,12 @@ const testWithHardhat: NewTaskActionFunction<TestActionArguments> = async (
7679
imports.push(tsx.href);
7780

7881
if (hre.globalOptions.coverage === true) {
79-
// NOTE: We set the HARDHAT_COVERAGE environment variable here because, as of now,
80-
// the global options are not automatically passed to the child processes.
81-
process.env.HARDHAT_COVERAGE = "true";
82-
8382
const coverage = new URL(
8483
import.meta.resolve("@nomicfoundation/hardhat-node-test-runner/coverage"),
8584
);
8685
imports.push(coverage.href);
8786
}
8887

89-
if (hre.globalOptions.network !== undefined) {
90-
process.env.HARDHAT_NETWORK = hre.globalOptions.network;
91-
}
92-
9388
process.env.NODE_OPTIONS = imports
9489
.map((href) => `--import "${href}"`)
9590
.join(" ");

v-next/hardhat-utils/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"./crypto": "./dist/src/crypto.js",
2121
"./date": "./dist/src/date.js",
2222
"./debug": "./dist/src/debug.js",
23+
"./env": "./dist/src/env.js",
2324
"./error": "./dist/src/error.js",
2425
"./eth": "./dist/src/eth.js",
2526
"./fs": "./dist/src/fs.js",
@@ -28,6 +29,7 @@
2829
"./lang": "./dist/src/lang.js",
2930
"./number": "./dist/src/number.js",
3031
"./package": "./dist/src/package.js",
32+
"./panic-errors": "./dist/src/panic-errors.js",
3133
"./path": "./dist/src/path.js",
3234
"./request": "./dist/src/request.js",
3335
"./string": "./dist/src/string.js",

v-next/hardhat-utils/src/env.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { camelToSnakeCase } from "./string.js";
2+
3+
/**
4+
* Sets the resolved global options as environment variables.
5+
*
6+
* @param globalOptions An object containing the resolved global options,
7+
* with each option adhering to its definition in the globalOptionDefinitions.
8+
*/
9+
export function setGlobalOptionsAsEnvVariables<
10+
T extends Record<keyof T, string | boolean>,
11+
>(globalOptions: T): void {
12+
for (const [name, value] of Object.entries(globalOptions)) {
13+
const envName = getEnvVariableNameFromGlobalOption(name);
14+
15+
if (value !== undefined) {
16+
process.env[envName] = String(value);
17+
}
18+
}
19+
}
20+
21+
/**
22+
* Converts a global option name to its corresponding environment variable name.
23+
* The conversion involves transforming the option name from camelCase to
24+
* SNAKE_CASE and prefixing it with "HARDHAT_".
25+
*
26+
* @param globalOptionName The name of the global option in camelCase.
27+
*
28+
* @returns The corresponding environment variable name in the format
29+
* "HARDHAT_<OPTION_NAME_IN_SNAKE_CASE>".
30+
*/
31+
export function getEnvVariableNameFromGlobalOption(globalOptionName: string) {
32+
return `HARDHAT_${camelToSnakeCase(globalOptionName).toUpperCase()}`;
33+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { numberToHexString } from "@nomicfoundation/hardhat-utils/hex";
1+
import { numberToHexString } from "./hex.js";
22

33
export function panicErrorCodeToMessage(errorCode: bigint): string {
44
const reason = panicErrorCodeToReason(errorCode);

v-next/hardhat-utils/test/env.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import assert from "node:assert/strict";
2+
import { describe, it } from "node:test";
3+
4+
import {
5+
getEnvVariableNameFromGlobalOption,
6+
setGlobalOptionsAsEnvVariables,
7+
} from "../src/env.js";
8+
9+
describe("env", () => {
10+
describe("getEnvVariableNameFromGlobalOption", () => {
11+
it("should load the global options in the env variables", async () => {
12+
setGlobalOptionsAsEnvVariables({
13+
network: "test-network",
14+
coverage: true,
15+
});
16+
17+
assert.equal(process.env.HARDHAT_NETWORK, "test-network");
18+
assert.equal(process.env.HARDHAT_COVERAGE, "true");
19+
});
20+
});
21+
22+
describe("getEnvVariableNameFromGlobalOption", () => {
23+
it("should return the global option name in env variable format", async () => {
24+
assert.equal(
25+
getEnvVariableNameFromGlobalOption("someOptionName"),
26+
"HARDHAT_SOME_OPTION_NAME",
27+
);
28+
});
29+
});
30+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { Hex } from "viem";
2+
3+
const ERROR_STRING_SELECTOR = "0x08c379a0"; // Error(string)
4+
const PANIC_SELECTOR = "0x4e487b71"; // Panic(uint256)
5+
6+
export function isKnownErrorSelector(data: Hex): boolean {
7+
const standardizedData = data.toLowerCase();
8+
9+
return (
10+
standardizedData.startsWith(ERROR_STRING_SELECTOR) ||
11+
standardizedData.startsWith(PANIC_SELECTOR)
12+
);
13+
}
14+
15+
export function isPanicErrorSelector(data: Hex): boolean {
16+
return data.toLowerCase().startsWith(PANIC_SELECTOR);
17+
}
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import type { Hex } from "viem";
22

33
import { assertHardhatInvariant } from "@nomicfoundation/hardhat-errors";
4+
import { ensureError } from "@nomicfoundation/hardhat-utils/error";
45
import { isPrefixedHexString } from "@nomicfoundation/hardhat-utils/hex";
56

67
/**
7-
* Recursively extracts, if it exists, the revert data hex string from a nested Viem error.
8+
* Recursively extracts, if it exists, the revert data hex string from an error.
89
*
910
* When a contract call reverts, Viem throws an `Error` whose `data` property
1011
* contains the hex-encoded revert reason. That `data` may live on the top-level
@@ -17,29 +18,41 @@ import { isPrefixedHexString } from "@nomicfoundation/hardhat-utils/hex";
1718
*
1819
* @throws If no valid `0x` prefixed hex string revert data is found anywhere in the error chain.
1920
*/
20-
export function extractRevertData(
21-
error: Error & { data?: unknown; cause?: unknown },
22-
): Hex {
23-
let errorData: Hex | undefined;
24-
let current: typeof error | undefined = error;
21+
export function extractRevertError(error: unknown): {
22+
name: string;
23+
message: string;
24+
data: Hex;
25+
} {
26+
ensureError(error);
2527

28+
let dataReason: Hex | undefined;
29+
30+
let current: Error | undefined = error;
31+
let message: string = "";
2632
while (current !== undefined) {
27-
const { data } = current;
33+
// Traverse the cause chain to find a valid raw error reason, if one exists.
34+
if ("data" in current) {
35+
const { data } = current;
2836

29-
if (typeof data === "string" && isPrefixedHexString(data)) {
30-
errorData = data;
37+
if (typeof data === "string" && isPrefixedHexString(data)) {
38+
dataReason = data;
39+
message = current.message;
40+
}
3141
}
3242

3343
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
3444
-- all the nested errors might contain a `cause` field */
35-
current = current.cause as typeof error | undefined;
45+
current = current.cause as Error | undefined;
3646
}
3747

38-
// In a revert scenario, a data field containing the revert reason is always expected
3948
assertHardhatInvariant(
40-
errorData !== undefined,
41-
"No revert data found on error",
49+
dataReason !== undefined,
50+
`No revert data found on error.\nError name: "${error.name}", message: ${error.message}`,
4251
);
4352

44-
return errorData;
53+
return {
54+
name: error.name,
55+
message,
56+
data: dataReason,
57+
};
4558
}

0 commit comments

Comments
 (0)