Skip to content

Commit

Permalink
Merge pull request #5914 from NomicFoundation/config-var-in-http-acco…
Browse files Browse the repository at this point in the history
…unts

Accept Configuration Variables in HTTP networks' `accounts`
  • Loading branch information
alcuadrado authored Oct 31, 2024
2 parents 5fff398 + b1566d6 commit f5be161
Show file tree
Hide file tree
Showing 31 changed files with 1,311 additions and 715 deletions.
15 changes: 14 additions & 1 deletion v-next/example-project/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import type { HardhatUserConfig } from "@ignored/hardhat-vnext/config";
import { HardhatPluginError } from "@ignored/hardhat-vnext/plugins";

import util from "node:util";
import { task, emptyTask, globalOption } from "@ignored/hardhat-vnext/config";
import {
task,
emptyTask,
globalOption,
configVariable,
} from "@ignored/hardhat-vnext/config";
import HardhatNodeTestRunner from "@ignored/hardhat-vnext-node-test-runner";
import HardhatMochaTestRunner from "@ignored/hardhat-vnext-mocha-test-runner";
import HardhatKeystore from "@ignored/hardhat-vnext-keystore";
Expand Down Expand Up @@ -106,6 +111,14 @@ const pluginExample = {
};

const config: HardhatUserConfig = {
networks: {
opSepolia: {
type: "http",
chainType: "optimism",
url: "https://sepolia.optimism.io",
accounts: [configVariable("OP_SEPOLIA_SENDER")],
},
},
tasks: [
exampleTaskOverride,
exampleEmptyTask,
Expand Down
52 changes: 52 additions & 0 deletions v-next/example-project/scripts/send-op-sepolia-tx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { network } from "@ignored/hardhat-vnext";

const { provider } = await network.connect("opSepolia", "optimism");

const accounts = (await provider.request({
method: "eth_accounts",
})) as string[];

console.log("Accounts:", accounts);

const sender = accounts[0];

console.log("Sender:", sender);

console.log(
"Sender balance: ",
(await provider.request({
method: "eth_getBalance",
params: [sender, "latest"],
})) as string,
);

console.log("Sending 1 gwei from", sender, "to", sender);

const tx = await provider.request({
method: "eth_sendTransaction",
params: [
{
from: sender,
to: sender,
value: "0x1",
},
],
});

console.log("Transaction hash:", tx);

while (true) {
console.log("Waiting for transaction to be mined...");
const receipt = await provider.request({
method: "eth_getTransactionReceipt",
params: [tx],
});

if (receipt === null) {
await new Promise((resolve) => setTimeout(resolve, 1000));
continue;
}

console.log("Transaction receipt:", receipt);
break;
}
10 changes: 9 additions & 1 deletion v-next/hardhat-errors/src/descriptors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,9 @@ Please double check whether you have multiple versions of the same plugin instal
},
ENV_VAR_NOT_FOUND: {
number: 7,
messageTemplate: "Configuration variable not found as an env variable",
messageTemplate: `Configuration Variable '{name}' not found.
You can define it using a plugin like hardhat-keystore, or set it as an environment variable.`,
websiteTitle: "Configuration variable not found",
websiteDescription: `A configuration variable was expected to be set as an environment variable, but it wasn't.`,
},
Expand Down Expand Up @@ -213,6 +215,12 @@ Please add the property "type" with the value "module" in your package.json to e
websiteTitle: "Workspace not found",
websiteDescription: `The workspace you provided does not exist. Please ensure that the workspace exists and try again.`,
},
INVALID_HEX_STRING: {
number: 18,
messageTemplate: `Invalid hex string "{value}"`,
websiteTitle: "Invalid hex string",
websiteDescription: `Given value was not a valid hex string.`,
},
},
INTERNAL: {
ASSERTION_ERROR: {
Expand Down
6 changes: 2 additions & 4 deletions v-next/hardhat-keystore/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { HardhatPlugin } from "@ignored/hardhat-vnext/types/plugins";

import "./internal/type-extensions.js";

import { task } from "@ignored/hardhat-vnext/config";
import { emptyTask, task } from "@ignored/hardhat-vnext/config";
import { ArgumentType } from "@ignored/hardhat-vnext/types/arguments";

import { PLUGIN_ID } from "./internal/constants.js";
Expand All @@ -16,9 +16,7 @@ const hardhatKeystorePlugin: HardhatPlugin = {
),
},
tasks: [
task("keystore", "Store your keys in a secure way")
.setAction(async () => {})
.build(),
emptyTask("keystore", "Store your keys in a secure way").build(),

task(
["keystore", "set"],
Expand Down
29 changes: 29 additions & 0 deletions v-next/hardhat-test-utils/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,32 @@ export async function assertRejects(

throw new Error("Function did not throw any error");
}

/**
* Asserts that a function throws an error.
*
* @param f The function to call.
* @param condition An optional condition that the error must satisfy.
* @param conditionDescription An optional description of the condition.
*/
export function assertThrows(
f: () => any,
condition?: (error: Error) => boolean,
conditionDescription: string = "Condition for error not met",
): void {
try {
f();
} catch (error) {
ensureError(error);

if (condition === undefined) {
return;
}

assert.ok(condition(error), conditionDescription);

return;
}

throw new Error("Function did not throw any error");
}
1 change: 1 addition & 0 deletions v-next/hardhat-test-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from "./fixture-projects.js";
export * from "./fs.js";
export * from "./hardhat-error.js";
export * from "./errors.js";
export * from "./validations.js";
95 changes: 95 additions & 0 deletions v-next/hardhat-test-utils/src/validations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import assert from "node:assert/strict";

/**
* We duplicate this to avoid a circular dependency between packages.
*/
export interface ValidationError {
path: Array<string | number>;
message: string;
}

/**
* An expected validation error, which is a ValidationError with an optional
* message. If the message is not provided, it's not asserted.
*/
export interface ExpectedValidationError {
path: Array<string | number>;
message?: string;
}

/**
* Asserts that the validations results are equal to the expected ones, ignoring
* the order of the errors.
*
* @param validationResult The results of running a validation function.
* @param expectedErrors The expected errors, which can omit the message, and
* don't need to be in the same order.
*/
export function assertValidationErrors(
validationResult: ValidationError[],
expectedErrors: ExpectedValidationError[],
): void {
const resultsByJsonPath = groupByJsonPath(validationResult);
const expectedErrorsByJsonPath = groupByJsonPath(expectedErrors);

for (const [jsonPath, expectedErrorsForPath] of expectedErrorsByJsonPath) {
const resultErrors = resultsByJsonPath.get(jsonPath);

if (resultErrors === undefined) {
assert.fail(
`Expected errors for path ${jsonPath} but none were found. Got these instead ${JSON.stringify(validationResult, undefined, 2)}`,
);

return;
}

for (const expectedError of expectedErrorsForPath) {
if (expectedError.message === undefined) {
continue;
}

const isPresent = resultErrors.some(
(r) => r.message === expectedError.message,
);

if (!isPresent) {
assert.fail(
`Expected an error for path ${jsonPath} to have message "${expectedError.message}" but found these instead ${JSON.stringify(resultErrors.map((e) => e.message))}`,
);
return;
}
}

if (expectedErrorsForPath.length !== resultErrors.length) {
assert.fail(
`Expected ${expectedErrorsForPath.length} errors for path ${jsonPath} but found ${resultErrors.length} instead: ${JSON.stringify(resultErrors.map((e) => e.message))}`,
);
}
}

for (const [jsonPath, resultErrors] of resultsByJsonPath) {
const expectedErrorsForPath = expectedErrorsByJsonPath.get(jsonPath);

if (expectedErrorsForPath === undefined) {
assert.fail(
`No errors were expected for path ${jsonPath} but found these instead ${JSON.stringify(resultErrors.map((e) => e.message))}`,
);
return;
}
}
}

function groupByJsonPath<
ErrorT extends ValidationError | ExpectedValidationError,
>(validationErrors: ErrorT[]): Map<string, ErrorT[]> {
const groupedByPath = new Map<string, ErrorT[]>();

for (const validationError of validationErrors) {
const jsonPath = JSON.stringify(validationError.path);
const errors = groupedByPath.get(jsonPath) ?? [];
errors.push(validationError);
groupedByPath.set(jsonPath, errors);
}

return groupedByPath;
}
69 changes: 68 additions & 1 deletion v-next/hardhat-test-utils/test/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { describe, it } from "node:test";

import { ensureError } from "@ignored/hardhat-vnext-utils/error";

import { assertRejects } from "../src/errors.js";
import { assertRejects, assertThrows } from "../src/errors.js";

describe("errors", () => {
describe("assertRejects", () => {
Expand Down Expand Up @@ -83,4 +83,71 @@ describe("errors", () => {
throw new Error("Function did not throw any error");
});
});

describe("assertThrows", () => {
it("Should pass if the function throws an error", async () => {
assertThrows(() => {
throw new Error("foo");
});
});

it("Should pass if the condition is met", async () => {
assertThrows(
() => {
throw new Error("foo");
},
(error) => error.message === "foo",
);
});

it("Should fail if the condition is not met", async () => {
try {
assertThrows(
() => {
throw new Error("foo");
},
(error) => error.message === "bar",
);
} catch (error) {
return;
}

assert.fail("Function did not throw any error");
});

it("Should use the correct error message if provided", async () => {
try {
assertThrows(
() => {
throw new Error("foo");
},
(error) => error.message === "bar",
"Custom error message",
);
} catch (error) {
ensureError(error);
assert.equal(
error.message,
"Custom error message",
"Custom error message should be used",
);
return;
}

assert.fail("Function did not throw any error");
});

it("Should fail if the value thrown is not an error", async () => {
try {
assertThrows(() => {
// eslint-disable-next-line no-throw-literal -- Intentional for the test
throw "foo";
});
} catch (error) {
return;
}

assert.fail("Function did not throw any error");
});
});
});
Loading

0 comments on commit f5be161

Please sign in to comment.