Skip to content

Commit

Permalink
Merge branch 'v-next' into move-chaintype-type
Browse files Browse the repository at this point in the history
  • Loading branch information
schaable authored Sep 28, 2024
2 parents a858309 + ce77ee9 commit 7c3054d
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 66 deletions.
22 changes: 12 additions & 10 deletions v-next/hardhat-zod-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { ZodTypeDef, ZodType } from "zod";

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

/**
Expand Down Expand Up @@ -138,29 +137,32 @@ export const incompatibleFieldType = (errorMessage = "Unexpected field") =>
/**
* A Zod type to validate Hardhat's ConfigurationVariable objects.
*/
export const configurationVariableType = z.object({
export const configurationVariableSchema = z.object({
_type: z.literal("ConfigurationVariable"),
name: z.string(),
});

/**
* A Zod type to validate Hardhat's SensitiveString values.
*/
export const sensitiveStringType = conditionalUnionType(
[
[(data) => typeof data === "string", z.string()],
[isObject, configurationVariableType],
],
export const sensitiveStringSchema = unionType(
[z.string(), configurationVariableSchema],
"Expected a string or a Configuration Variable",
);

/**
* A Zod type to validate Hardhat's SensitiveString values that expect a URL.
*
* TODO: The custom error message in the unionType function doesn't work
* correctly when using string().url() for validation, see:
* https://github.com/colinhacks/zod/issues/2940
* As a workaround, we provide the error message directly in the url() call.
* We should remove this when the issue is fixed.
*/
export const sensitiveUrlType = conditionalUnionType(
export const sensitiveUrlSchema = unionType(
[
[(data) => typeof data === "string", z.string().url()],
[isObject, configurationVariableType],
z.string().url("Expected a URL or a Configuration Variable"),
configurationVariableSchema,
],
"Expected a URL or a Configuration Variable",
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import type {
import type { HardhatUserConfigValidationError } from "../../../types/hooks.js";

import {
sensitiveUrlType,
conditionalUnionType,
sensitiveUrlSchema,
unionType,
validateUserConfigZodType,
} from "@ignored/hardhat-vnext-zod-utils";
Expand All @@ -16,27 +17,26 @@ const chainTypeSchema = unionType(
"Expected 'l1', 'optimism', or 'unknown'",
);

const userGasSchema = conditionalUnionType(
[
[(data) => typeof data === "string", z.literal("auto")],
[(data) => typeof data === "number", z.number().int().positive().safe()],
[(data) => typeof data === "bigint", z.bigint().positive()],
],
"Expected 'auto', a safe int, or bigint",
);

const httpNetworkUserConfigSchema = z.object({
type: z.literal("http"),
chainId: z.optional(z.number().int()),
chainType: z.optional(chainTypeSchema),
from: z.optional(z.string()),
gas: z.optional(
unionType(
[z.literal("auto"), z.number().int().safe(), z.bigint()],
"Expected 'auto', a safe int, or bigint",
),
),
gas: z.optional(userGasSchema),
gasMultiplier: z.optional(z.number()),
gasPrice: z.optional(
unionType(
[z.literal("auto"), z.number().int().safe(), z.bigint()],
"Expected 'auto', a safe int, or bigint",
),
),
gasPrice: z.optional(userGasSchema),

// HTTP network specific
url: sensitiveUrlType,
url: sensitiveUrlSchema,
timeout: z.optional(z.number()),
httpHeaders: z.optional(z.record(z.string())),
});
Expand All @@ -46,15 +46,9 @@ const edrNetworkUserConfigSchema = z.object({
chainId: z.number().int(),
chainType: z.optional(chainTypeSchema),
from: z.optional(z.string()),
gas: unionType(
[z.literal("auto"), z.number().int().safe(), z.bigint()],
"Expected 'auto', a safe int, or bigint",
),
gas: userGasSchema,
gasMultiplier: z.number(),
gasPrice: unionType(
[z.literal("auto"), z.number().int().safe(), z.bigint()],
"Expected 'auto', a safe int, or bigint",
),
gasPrice: userGasSchema,
});

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

const gasSchema = conditionalUnionType(
[
[(data) => typeof data === "string", z.literal("auto")],
[(data) => typeof data === "bigint", z.bigint().positive()],
],
"Expected 'auto' or bigint",
);

const httpNetworkConfigSchema = z.object({
type: z.literal("http"),
chainId: z.optional(z.number().int()),
chainType: z.optional(chainTypeSchema),
from: z.optional(z.string()),
gas: unionType([z.literal("auto"), z.bigint()], "Expected 'auto' or bigint"),
gas: gasSchema,
gasMultiplier: z.number(),
gasPrice: unionType(
[z.literal("auto"), z.bigint()],
"Expected 'auto' or bigint",
),
gasPrice: gasSchema,

// HTTP network specific
url: sensitiveUrlType,
url: sensitiveUrlSchema,
timeout: z.number(),
httpHeaders: z.record(z.string()),
});
Expand All @@ -91,12 +90,9 @@ const edrNetworkConfigSchema = z.object({
chainId: z.number().int(),
chainType: z.optional(chainTypeSchema),
from: z.string(),
gas: unionType([z.literal("auto"), z.bigint()], "Expected 'auto' or bigint"),
gas: gasSchema,
gasMultiplier: z.number(),
gasPrice: unionType(
[z.literal("auto"), z.bigint()],
"Expected 'auto' or bigint",
),
gasPrice: gasSchema,
});

const networkConfigSchema = z.discriminatedUnion("type", [
Expand Down
22 changes: 13 additions & 9 deletions v-next/hardhat/src/internal/core/hre.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,6 @@ export class HardhatRuntimeEnvironmentImplementation

const interruptions = new UserInterruptionManagerImplementation(hooks);

const hookContext: HookContext = {
hooks,
config,
globalOptions,
interruptions,
};

hooks.setContext(hookContext);

const hre = new HardhatRuntimeEnvironmentImplementation(
extendedUserConfig,
config,
Expand All @@ -136,6 +127,19 @@ export class HardhatRuntimeEnvironmentImplementation
globalOptionDefinitions,
);

// We create an object with the HRE as its prototype, and overwrite the
// tasks property with undefined, so that hooks don't have access to the
// task runner.
//
// The reason we do this with a prototype instead of a shallow copy is that
// the handlers hooked into hre/created may assign new properties to the
// HRE and we want those to be accessible to all the handlers.
const hookContext: HookContext = Object.create(hre, {
tasks: { value: undefined },
});

hooks.setContext(hookContext);

await hooks.runSequentialHandlers("hre", "created", [hre]);

return hre;
Expand Down
9 changes: 1 addition & 8 deletions v-next/hardhat/src/types/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import type {
HardhatUserConfig,
ResolvedConfigurationVariable,
} from "./config.js";
import type { GlobalOptions } from "./global-options.js";
import type { HardhatRuntimeEnvironment } from "./hre.js";
import type { UserInterruptionManager } from "./user-interruptions.js";
import type {
LastParameter,
ParametersExceptFirst,
Expand All @@ -32,12 +30,7 @@ declare module "./hre.js" {
* The `HookContext` offers a subset of the functionality that the
* `HardhatRuntimeEnvironment` does.
*/
export interface HookContext {
readonly config: HardhatConfig;
readonly globalOptions: GlobalOptions;
readonly interruptions: UserInterruptionManager;
readonly hooks: HookManager;
}
export type HookContext = Omit<HardhatRuntimeEnvironment, "tasks">;

/**
* The different hooks that a plugin can define handlers for.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ describe("network-manager/hook-handlers/config", () => {
);
assert.equal(
validationErrors[0].message,
"Expected 'auto', a safe int, or bigint",
`Invalid literal value, expected "auto"`,
);

const configWithNonSafeIntGas = {
Expand All @@ -340,11 +340,37 @@ describe("network-manager/hook-handlers/config", () => {
validationErrors.length > 0,
"validation errors should be present",
);
// TODO: the error message should be "Expected 'auto', a safe int, or bigint"

assert.equal(
validationErrors[0].message,
"Number must be less than or equal to 9007199254740991",
);

const configWithNegativeGas = {
networks: {
localhost: {
type: "http",
url: "http://localhost:8545",
gas: -100,
},
},
};

validationErrors = await validateUserConfig(
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
-- testing invalid network type for js users */
configWithNegativeGas as any,
);

assert.ok(
validationErrors.length > 0,
"validation errors should be present",
);

assert.equal(
validationErrors[0].message,
"Number must be greater than 0",
);
});

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

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

// TODO: the error message should be "Expected 'auto', a safe int, or bigint"
assert.equal(
validationErrors[0].message,
"Number must be less than or equal to 9007199254740991",
);

const configWithNegativeGasPrice = {
networks: {
localhost: {
type: "http",
url: "http://localhost:8545",
gasPrice: -100,
},
},
};

validationErrors = await validateUserConfig(
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
-- testing invalid network type for js users */
configWithNegativeGasPrice as any,
);

assert.ok(
validationErrors.length > 0,
"validation errors should be present",
);

assert.equal(
validationErrors[0].message,
"Number must be greater than 0",
);
});

describe("http network specific fields", () => {
Expand Down Expand Up @@ -468,8 +519,10 @@ describe("network-manager/hook-handlers/config", () => {
validationErrors.length > 0,
"validation errors should be present",
);
// TODO: the error message should be "Expected a URL or a Configuration Variable"
assert.equal(validationErrors[0].message, "Invalid url");
assert.equal(
validationErrors[0].message,
"Expected a URL or a Configuration Variable",
);
});

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

0 comments on commit 7c3054d

Please sign in to comment.