Skip to content

Commit 7c3054d

Browse files
authored
Merge branch 'v-next' into move-chaintype-type
2 parents a858309 + ce77ee9 commit 7c3054d

File tree

6 files changed

+185
-66
lines changed

6 files changed

+185
-66
lines changed

v-next/hardhat-zod-utils/src/index.ts

+12-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { ZodTypeDef, ZodType } from "zod";
22

3-
import { isObject } from "@ignored/hardhat-vnext-utils/lang";
43
import { z } from "zod";
54

65
/**
@@ -138,29 +137,32 @@ export const incompatibleFieldType = (errorMessage = "Unexpected field") =>
138137
/**
139138
* A Zod type to validate Hardhat's ConfigurationVariable objects.
140139
*/
141-
export const configurationVariableType = z.object({
140+
export const configurationVariableSchema = z.object({
142141
_type: z.literal("ConfigurationVariable"),
143142
name: z.string(),
144143
});
145144

146145
/**
147146
* A Zod type to validate Hardhat's SensitiveString values.
148147
*/
149-
export const sensitiveStringType = conditionalUnionType(
150-
[
151-
[(data) => typeof data === "string", z.string()],
152-
[isObject, configurationVariableType],
153-
],
148+
export const sensitiveStringSchema = unionType(
149+
[z.string(), configurationVariableSchema],
154150
"Expected a string or a Configuration Variable",
155151
);
156152

157153
/**
158154
* A Zod type to validate Hardhat's SensitiveString values that expect a URL.
155+
*
156+
* TODO: The custom error message in the unionType function doesn't work
157+
* correctly when using string().url() for validation, see:
158+
* https://github.com/colinhacks/zod/issues/2940
159+
* As a workaround, we provide the error message directly in the url() call.
160+
* We should remove this when the issue is fixed.
159161
*/
160-
export const sensitiveUrlType = conditionalUnionType(
162+
export const sensitiveUrlSchema = unionType(
161163
[
162-
[(data) => typeof data === "string", z.string().url()],
163-
[isObject, configurationVariableType],
164+
z.string().url("Expected a URL or a Configuration Variable"),
165+
configurationVariableSchema,
164166
],
165167
"Expected a URL or a Configuration Variable",
166168
);

v-next/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts

+29-33
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import type {
55
import type { HardhatUserConfigValidationError } from "../../../types/hooks.js";
66

77
import {
8-
sensitiveUrlType,
8+
conditionalUnionType,
9+
sensitiveUrlSchema,
910
unionType,
1011
validateUserConfigZodType,
1112
} from "@ignored/hardhat-vnext-zod-utils";
@@ -16,27 +17,26 @@ const chainTypeSchema = unionType(
1617
"Expected 'l1', 'optimism', or 'unknown'",
1718
);
1819

20+
const userGasSchema = conditionalUnionType(
21+
[
22+
[(data) => typeof data === "string", z.literal("auto")],
23+
[(data) => typeof data === "number", z.number().int().positive().safe()],
24+
[(data) => typeof data === "bigint", z.bigint().positive()],
25+
],
26+
"Expected 'auto', a safe int, or bigint",
27+
);
28+
1929
const httpNetworkUserConfigSchema = z.object({
2030
type: z.literal("http"),
2131
chainId: z.optional(z.number().int()),
2232
chainType: z.optional(chainTypeSchema),
2333
from: z.optional(z.string()),
24-
gas: z.optional(
25-
unionType(
26-
[z.literal("auto"), z.number().int().safe(), z.bigint()],
27-
"Expected 'auto', a safe int, or bigint",
28-
),
29-
),
34+
gas: z.optional(userGasSchema),
3035
gasMultiplier: z.optional(z.number()),
31-
gasPrice: z.optional(
32-
unionType(
33-
[z.literal("auto"), z.number().int().safe(), z.bigint()],
34-
"Expected 'auto', a safe int, or bigint",
35-
),
36-
),
36+
gasPrice: z.optional(userGasSchema),
3737

3838
// HTTP network specific
39-
url: sensitiveUrlType,
39+
url: sensitiveUrlSchema,
4040
timeout: z.optional(z.number()),
4141
httpHeaders: z.optional(z.record(z.string())),
4242
});
@@ -46,15 +46,9 @@ const edrNetworkUserConfigSchema = z.object({
4646
chainId: z.number().int(),
4747
chainType: z.optional(chainTypeSchema),
4848
from: z.optional(z.string()),
49-
gas: unionType(
50-
[z.literal("auto"), z.number().int().safe(), z.bigint()],
51-
"Expected 'auto', a safe int, or bigint",
52-
),
49+
gas: userGasSchema,
5350
gasMultiplier: z.number(),
54-
gasPrice: unionType(
55-
[z.literal("auto"), z.number().int().safe(), z.bigint()],
56-
"Expected 'auto', a safe int, or bigint",
57-
),
51+
gasPrice: userGasSchema,
5852
});
5953

6054
const networkUserConfigSchema = z.discriminatedUnion("type", [
@@ -68,20 +62,25 @@ const userConfigSchema = z.object({
6862
networks: z.optional(z.record(networkUserConfigSchema)),
6963
});
7064

65+
const gasSchema = conditionalUnionType(
66+
[
67+
[(data) => typeof data === "string", z.literal("auto")],
68+
[(data) => typeof data === "bigint", z.bigint().positive()],
69+
],
70+
"Expected 'auto' or bigint",
71+
);
72+
7173
const httpNetworkConfigSchema = z.object({
7274
type: z.literal("http"),
7375
chainId: z.optional(z.number().int()),
7476
chainType: z.optional(chainTypeSchema),
7577
from: z.optional(z.string()),
76-
gas: unionType([z.literal("auto"), z.bigint()], "Expected 'auto' or bigint"),
78+
gas: gasSchema,
7779
gasMultiplier: z.number(),
78-
gasPrice: unionType(
79-
[z.literal("auto"), z.bigint()],
80-
"Expected 'auto' or bigint",
81-
),
80+
gasPrice: gasSchema,
8281

8382
// HTTP network specific
84-
url: sensitiveUrlType,
83+
url: sensitiveUrlSchema,
8584
timeout: z.number(),
8685
httpHeaders: z.record(z.string()),
8786
});
@@ -91,12 +90,9 @@ const edrNetworkConfigSchema = z.object({
9190
chainId: z.number().int(),
9291
chainType: z.optional(chainTypeSchema),
9392
from: z.string(),
94-
gas: unionType([z.literal("auto"), z.bigint()], "Expected 'auto' or bigint"),
93+
gas: gasSchema,
9594
gasMultiplier: z.number(),
96-
gasPrice: unionType(
97-
[z.literal("auto"), z.bigint()],
98-
"Expected 'auto' or bigint",
99-
),
95+
gasPrice: gasSchema,
10096
});
10197

10298
const networkConfigSchema = z.discriminatedUnion("type", [

v-next/hardhat/src/internal/core/hre.ts

+13-9
Original file line numberDiff line numberDiff line change
@@ -118,15 +118,6 @@ export class HardhatRuntimeEnvironmentImplementation
118118

119119
const interruptions = new UserInterruptionManagerImplementation(hooks);
120120

121-
const hookContext: HookContext = {
122-
hooks,
123-
config,
124-
globalOptions,
125-
interruptions,
126-
};
127-
128-
hooks.setContext(hookContext);
129-
130121
const hre = new HardhatRuntimeEnvironmentImplementation(
131122
extendedUserConfig,
132123
config,
@@ -136,6 +127,19 @@ export class HardhatRuntimeEnvironmentImplementation
136127
globalOptionDefinitions,
137128
);
138129

130+
// We create an object with the HRE as its prototype, and overwrite the
131+
// tasks property with undefined, so that hooks don't have access to the
132+
// task runner.
133+
//
134+
// The reason we do this with a prototype instead of a shallow copy is that
135+
// the handlers hooked into hre/created may assign new properties to the
136+
// HRE and we want those to be accessible to all the handlers.
137+
const hookContext: HookContext = Object.create(hre, {
138+
tasks: { value: undefined },
139+
});
140+
141+
hooks.setContext(hookContext);
142+
139143
await hooks.runSequentialHandlers("hre", "created", [hre]);
140144

141145
return hre;

v-next/hardhat/src/types/hooks.ts

+1-8
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ import type {
44
HardhatUserConfig,
55
ResolvedConfigurationVariable,
66
} from "./config.js";
7-
import type { GlobalOptions } from "./global-options.js";
87
import type { HardhatRuntimeEnvironment } from "./hre.js";
9-
import type { UserInterruptionManager } from "./user-interruptions.js";
108
import type {
119
LastParameter,
1210
ParametersExceptFirst,
@@ -32,12 +30,7 @@ declare module "./hre.js" {
3230
* The `HookContext` offers a subset of the functionality that the
3331
* `HardhatRuntimeEnvironment` does.
3432
*/
35-
export interface HookContext {
36-
readonly config: HardhatConfig;
37-
readonly globalOptions: GlobalOptions;
38-
readonly interruptions: UserInterruptionManager;
39-
readonly hooks: HookManager;
40-
}
33+
export type HookContext = Omit<HardhatRuntimeEnvironment, "tasks">;
4134

4235
/**
4336
* The different hooks that a plugin can define handlers for.

v-next/hardhat/test/internal/builtin-plugins/network-manager/hook-handlers/config.ts

+59-6
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ describe("network-manager/hook-handlers/config", () => {
317317
);
318318
assert.equal(
319319
validationErrors[0].message,
320-
"Expected 'auto', a safe int, or bigint",
320+
`Invalid literal value, expected "auto"`,
321321
);
322322

323323
const configWithNonSafeIntGas = {
@@ -340,11 +340,37 @@ describe("network-manager/hook-handlers/config", () => {
340340
validationErrors.length > 0,
341341
"validation errors should be present",
342342
);
343-
// TODO: the error message should be "Expected 'auto', a safe int, or bigint"
343+
344344
assert.equal(
345345
validationErrors[0].message,
346346
"Number must be less than or equal to 9007199254740991",
347347
);
348+
349+
const configWithNegativeGas = {
350+
networks: {
351+
localhost: {
352+
type: "http",
353+
url: "http://localhost:8545",
354+
gas: -100,
355+
},
356+
},
357+
};
358+
359+
validationErrors = await validateUserConfig(
360+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
361+
-- testing invalid network type for js users */
362+
configWithNegativeGas as any,
363+
);
364+
365+
assert.ok(
366+
validationErrors.length > 0,
367+
"validation errors should be present",
368+
);
369+
370+
assert.equal(
371+
validationErrors[0].message,
372+
"Number must be greater than 0",
373+
);
348374
});
349375

350376
it("should throw if the gasMultiplier is invalid", async () => {
@@ -395,7 +421,7 @@ describe("network-manager/hook-handlers/config", () => {
395421
);
396422
assert.equal(
397423
validationErrors[0].message,
398-
"Expected 'auto', a safe int, or bigint",
424+
`Invalid literal value, expected "auto"`,
399425
);
400426

401427
const configWithNonSafeIntGasPrice = {
@@ -419,11 +445,36 @@ describe("network-manager/hook-handlers/config", () => {
419445
"validation errors should be present",
420446
);
421447

422-
// TODO: the error message should be "Expected 'auto', a safe int, or bigint"
423448
assert.equal(
424449
validationErrors[0].message,
425450
"Number must be less than or equal to 9007199254740991",
426451
);
452+
453+
const configWithNegativeGasPrice = {
454+
networks: {
455+
localhost: {
456+
type: "http",
457+
url: "http://localhost:8545",
458+
gasPrice: -100,
459+
},
460+
},
461+
};
462+
463+
validationErrors = await validateUserConfig(
464+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
465+
-- testing invalid network type for js users */
466+
configWithNegativeGasPrice as any,
467+
);
468+
469+
assert.ok(
470+
validationErrors.length > 0,
471+
"validation errors should be present",
472+
);
473+
474+
assert.equal(
475+
validationErrors[0].message,
476+
"Number must be greater than 0",
477+
);
427478
});
428479

429480
describe("http network specific fields", () => {
@@ -468,8 +519,10 @@ describe("network-manager/hook-handlers/config", () => {
468519
validationErrors.length > 0,
469520
"validation errors should be present",
470521
);
471-
// TODO: the error message should be "Expected a URL or a Configuration Variable"
472-
assert.equal(validationErrors[0].message, "Invalid url");
522+
assert.equal(
523+
validationErrors[0].message,
524+
"Expected a URL or a Configuration Variable",
525+
);
473526
});
474527

475528
it("should throw if the timeout is invalid", async () => {

0 commit comments

Comments
 (0)