Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move V2 wrapped providers to v3 #5810

Merged
merged 27 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
552addd
hook to initialize providers
ChristopherDedominici Oct 7, 2024
1ee1274
add chain-id-validator
ChristopherDedominici Oct 7, 2024
057e42f
fixed-gas-provider
ChristopherDedominici Oct 7, 2024
62cf000
add FixedGasPriceProvider
ChristopherDedominici Oct 7, 2024
9c214fb
fix tests' descriptions for chain-id-validator
ChristopherDedominici Oct 7, 2024
44dc54b
MultipliedGasEstimationProvider
ChristopherDedominici Oct 7, 2024
6af8c34
add AutomaticGasProvider + tests
ChristopherDedominici Oct 7, 2024
9e959d2
add AutomaticGasPriceProvider
ChristopherDedominici Oct 8, 2024
93b2962
move MultipliedGasEstimationProvider to the correct folder
ChristopherDedominici Oct 8, 2024
0aacd35
rename classes and files: remove 'provider' from name
ChristopherDedominici Oct 8, 2024
7cad76c
modify the hook to use the class JsonRequestModifier
ChristopherDedominici Oct 8, 2024
1791379
add 'accounts' to the network configuration
ChristopherDedominici Oct 9, 2024
21f2a8c
refactor and reorganize files
ChristopherDedominici Oct 9, 2024
28e0c2c
revert changes about the 'accounts' property
ChristopherDedominici Oct 9, 2024
6a709f5
rename test folder
ChristopherDedominici Oct 9, 2024
de57894
fix test descriptions and clean unused variable
ChristopherDedominici Oct 9, 2024
060eb84
Fix comment
ChristopherDedominici Oct 10, 2024
7068fd0
Merge branch 'v-next' of github.com:NomicFoundation/hardhat into move…
ChristopherDedominici Oct 10, 2024
fc97d85
Merge branch 'move-providers-to-v3' of github.com:NomicFoundation/har…
ChristopherDedominici Oct 10, 2024
942059a
Merge branch 'v-next' of github.com:NomicFoundation/hardhat into move…
ChristopherDedominici Oct 15, 2024
b7b80c1
add hexStringToNumber in hh-utils and replace in code instead of rpcQ…
ChristopherDedominici Oct 17, 2024
69f653c
use 'isObject'
ChristopherDedominici Oct 17, 2024
c163221
replace helper methods
ChristopherDedominici Oct 17, 2024
88f2175
add docs to briefly describe what the classes do
ChristopherDedominici Oct 17, 2024
a53beac
modify constructor to accept hex string
ChristopherDedominici Oct 17, 2024
2d3ff5b
remove casting from getParams, rename to getRequestParams and move it…
ChristopherDedominici Oct 17, 2024
6472432
rename isResolvedHttpNetworkConfig to isHttpNetworkConfig
ChristopherDedominici Oct 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions v-next/hardhat-errors/src/descriptors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,21 @@ If you want to use a different chain type, please update your networks config.`,
websiteTitle: "Invalid config override",
websiteDescription: `The configuration override you provided is invalid.`,
},
INVALID_GLOBAL_CHAIN_ID: {
number: 708,
messageTemplate:
"Hardhat was set to use chain id {configChainId}, but connected to a chain with id {connectionChainId}.",
websiteTitle: "Invalid global chain id",
websiteDescription: `Hardhat was set to use a chain id but connected to a chain with a different id`,
},
NO_REMOTE_ACCOUNT_AVAILABLE: {
number: 709,
messageTemplate:
"No local account was set and there are accounts in the remote node.",
websiteTitle: "No remote account available",
websiteDescription:
"No local account was set and there are accounts in the remote node",
},
},
KEYSTORE: {
INVALID_KEYSTORE_FILE_FORMAT: {
Expand Down
36 changes: 36 additions & 0 deletions v-next/hardhat-utils/src/hex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,42 @@ export function hexStringToBigInt(hexString: string): bigint {
return bigInt;
}

/**
* Converts a hexadecimal string to a number. The string must be a valid
* hexadecimal string. The string may be prefixed with "0x" or not. The
* empty string is considered a valid hexadecimal string, so is the string
* "0x" and will be converted to 0.
*
* @param hexString The hexadecimal string to convert. It must be a valid
* hexadecimal string.
* @returns The number representation of the hexadecimal string.
* @throws InvalidParameterError If the input is not a hexadecimal string or the value exceeds the Number.MAX_SAFE_INTEGER limit.
*/
export function hexStringToNumber(hexString: string): number {
if (!isHexString(hexString)) {
throw new InvalidParameterError(
`Expected a valid hexadecimal string. Received: ${hexString}`,
);
}

// Prefix the string as it is required to make parseInt interpret it as a
// hexadecimal number.
let prefixedHexString = getPrefixedHexString(hexString);

// Handle the special case where the string is "0x".
prefixedHexString = prefixedHexString === "0x" ? "0x0" : prefixedHexString;

const numberValue = parseInt(prefixedHexString, 16);

if (numberValue > Number.MAX_SAFE_INTEGER) {
throw new InvalidParameterError(
`Value exceeds the safe integer limit. Received: ${hexString}`,
);
}

return numberValue;
}

/**
* Converts a Uint8Array to a hexadecimal string.
*
Expand Down
52 changes: 50 additions & 2 deletions v-next/hardhat-utils/test/hex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
getPrefixedHexString,
unpadHexString,
setLengthLeft,
hexStringToNumber,
} from "../src/hex.js";

describe("hex", () => {
Expand Down Expand Up @@ -72,8 +73,8 @@ describe("hex", () => {
});
});

describe("hexStringToNumber", () => {
it("Should convert a hexadecimal string to a number", () => {
describe("hexStringToBigInt", () => {
it("Should convert a hexadecimal string to a bigint", () => {
assert.equal(hexStringToBigInt("0x"), 0n);
assert.equal(hexStringToBigInt("0x0"), 0n);
assert.equal(hexStringToBigInt("0x1"), 1n);
Expand Down Expand Up @@ -107,6 +108,53 @@ describe("hex", () => {
});
});

describe("hexStringToNumber", () => {
it("Should convert a hexadecimal string to a number", () => {
assert.equal(hexStringToNumber("0x"), 0);
assert.equal(hexStringToNumber("0x0"), 0);
assert.equal(hexStringToNumber("0x1"), 1);
assert.equal(hexStringToNumber("0xf"), 15);
assert.equal(hexStringToNumber("0x10"), 16);
assert.equal(hexStringToNumber("0xff"), 255);
assert.equal(hexStringToNumber("0x100"), 256);
assert.equal(hexStringToNumber("0xffff"), 65535);
assert.equal(hexStringToNumber("0x10000"), 65536);
assert.equal(hexStringToNumber("0xffffffff"), 4294967295);
assert.equal(hexStringToNumber("0x100000000"), 4294967296);

assert.equal(hexStringToNumber(""), 0);
assert.equal(hexStringToNumber("0"), 0);
assert.equal(hexStringToNumber("1"), 1);
assert.equal(hexStringToNumber("f"), 15);
assert.equal(hexStringToNumber("10"), 16);
assert.equal(hexStringToNumber("ff"), 255);
assert.equal(hexStringToNumber("100"), 256);
assert.equal(hexStringToNumber("ffff"), 65535);
assert.equal(hexStringToNumber("10000"), 65536);
assert.equal(hexStringToNumber("ffffffff"), 4294967295);
assert.equal(hexStringToNumber("100000000"), 4294967296);
assert.equal(hexStringToNumber("ffffffff"), 4294967295);

// Max allowed value
assert.equal(hexStringToNumber("0x1fffffffffffff"), 9007199254740991);
});

it("Should throw InvalidParameterError if the input exceeds the safe integer limit", () => {
assert.throws(() => hexStringToNumber("0x20000000000000"), {
name: "InvalidParameterError",
message:
"Value exceeds the safe integer limit. Received: 0x20000000000000",
});
});

it("Should throw InvalidParameterError if the input is not a hexadecimal string", () => {
assert.throws(() => hexStringToNumber("invalid"), {
name: "InvalidParameterError",
message: "Expected a valid hexadecimal string. Received: invalid",
});
});
});

describe("bytesToHexString", () => {
it("Should convert a Uint8Array to a hexadecimal string", () => {
assert.equal(bytesToHexString(new Uint8Array([])), "0x");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type {
JsonRpcRequest,
JsonRpcResponse,
} from "../../../../types/providers.js";
import type {
HookContext,
NetworkHooks,
} from "@ignored/hardhat-vnext/types/hooks";
import type {
ChainType,
NetworkConnection,
} from "@ignored/hardhat-vnext/types/network";

import { JsonRpcRequestModifier } from "../json-rpc-request-modifiers/json-rpc-request-modifier.js";

export default async (): Promise<Partial<NetworkHooks>> => {
// This map is necessary because Hardhat V3 supports multiple network connections, requiring us to track them
// to apply the appropriate modifiers to each request.
// When a connection is closed, it is removed from the map. Refer to "closeConnection" at the end of the file.
const jsonRpcRequestModifiers: Map<number, JsonRpcRequestModifier> =
new Map();

const handlers: Partial<NetworkHooks> = {
async onRequest<ChainTypeT extends ChainType | string>(
context: HookContext,
networkConnection: NetworkConnection<ChainTypeT>,
jsonRpcRequest: JsonRpcRequest,
next: (
nextContext: HookContext,
nextNetworkConnection: NetworkConnection<ChainTypeT>,
nextJsonRpcRequest: JsonRpcRequest,
) => Promise<JsonRpcResponse>,
) {
let jsonRpcRequestModifier = jsonRpcRequestModifiers.get(
networkConnection.id,
);

if (jsonRpcRequestModifier === undefined) {
jsonRpcRequestModifier = new JsonRpcRequestModifier(networkConnection);

jsonRpcRequestModifiers.set(
networkConnection.id,
jsonRpcRequestModifier,
);
}

const newJsonRpcRequest =
await jsonRpcRequestModifier.createModifiedJsonRpcRequest(
jsonRpcRequest,
);

return next(context, networkConnection, newJsonRpcRequest);
},

async closeConnection<ChainTypeT extends ChainType | string>(
context: HookContext,
networkConnection: NetworkConnection<ChainTypeT>,
next: (
nextContext: HookContext,
nextNetworkConnection: NetworkConnection<ChainTypeT>,
) => Promise<void>,
): Promise<void> {
if (jsonRpcRequestModifiers.has(networkConnection.id) === true) {
jsonRpcRequestModifiers.delete(networkConnection.id);
}

return next(context, networkConnection);
},
};

return handlers;
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const hardhatPlugin: HardhatPlugin = {
hookHandlers: {
config: import.meta.resolve("./hook-handlers/config.js"),
hre: import.meta.resolve("./hook-handlers/hre.js"),
network: import.meta.resolve("./hook-handlers/network.js"),
},
globalOptions: [
globalOption({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { EthereumProvider } from "../../../../../types/providers.js";

import { HardhatError } from "@ignored/hardhat-vnext-errors";

import { ChainId } from "./chain-id.js";

/**
* This class extends `ChainId` to validate that the current provider's chain ID matches
* an expected value. If the actual chain ID differs from the expected one, it throws a
* HardhatError to signal a network configuration mismatch. Once validated, further checks
* are skipped to avoid redundant validations.
*/
export class ChainIdValidator extends ChainId {
readonly #expectedChainId: number;

#alreadyValidated = false;
readonly #chainId: number | undefined;

constructor(provider: EthereumProvider, expectedChainId: number) {
super(provider);
this.#expectedChainId = expectedChainId;
}

public async validate(): Promise<void> {
if (!this.#alreadyValidated) {
const actualChainId = await this.getChainId();

if (actualChainId !== this.#expectedChainId) {
throw new HardhatError(
HardhatError.ERRORS.NETWORK.INVALID_GLOBAL_CHAIN_ID,
{
configChainId: this.#expectedChainId,
connectionChainId: actualChainId,
},
);
}

this.#alreadyValidated = true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { EthereumProvider } from "../../../../../types/providers.js";

import { assertHardhatInvariant } from "@ignored/hardhat-vnext-errors";
import {
hexStringToNumber,
isHexStringPrefixed,
} from "@ignored/hardhat-vnext-utils/hex";

/**
* This class is responsible for retrieving the chain ID of the network.
* It uses the provider to fetch the chain ID via two methods: 'eth_chainId' and,
* as a fallback, 'net_version' if the first one fails. The chain ID is cached
* after being retrieved to avoid redundant requests.
*/
export abstract class ChainId {
protected readonly provider: EthereumProvider;

#chainId: number | undefined;

constructor(provider: EthereumProvider) {
this.provider = provider;
}

protected async getChainId(): Promise<number> {
if (this.#chainId === undefined) {
try {
this.#chainId = await this.getChainIdFromEthChainId();
} catch {
// If eth_chainId fails we default to net_version
this.#chainId = await this.#getChainIdFromEthNetVersion();
}
}

return this.#chainId;
}

protected async getChainIdFromEthChainId(): Promise<number> {
const id = await this.provider.request({
method: "eth_chainId",
});

assertHardhatInvariant(typeof id === "string", "id should be a string");

return hexStringToNumber(id);
}

async #getChainIdFromEthNetVersion(): Promise<number> {
const id = await this.provider.request({
method: "net_version",
});

assertHardhatInvariant(typeof id === "string", "id should be a string");

// There's a node returning this as decimal instead of QUANTITY.
// TODO: from V2 - Document here which node does that
return isHexStringPrefixed(id) ? hexStringToNumber(id) : parseInt(id, 10);
}
}
Loading