Skip to content

Commit

Permalink
Merge pull request #6065 from NomicFoundation/edr-config-validation
Browse files Browse the repository at this point in the history
EDR Config validation
  • Loading branch information
schaable authored Jan 14, 2025
2 parents 34e9feb + c74a693 commit c8adb6d
Show file tree
Hide file tree
Showing 17 changed files with 644 additions and 330 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import type { SensitiveString } from "../../../../types/config.js";

import { HardhatError } from "@ignored/hardhat-vnext-errors";
import { mnemonicToSeedSync } from "ethereum-cryptography/bip39";
import { HDKey } from "ethereum-cryptography/hdkey";

const HD_PATH_REGEX = /^m(:?\/\d+'?)+\/?$/;

export const DEFAULT_HD_ACCOUNTS_CONFIG_PARAMS = {
initialIndex: 0,
count: 20,
path: "m/44'/60'/0'/0",
passphrase: "",
};
export interface DefaultHDAccountsConfigParams {
initialIndex: number;
count: number;
path: string;
passphrase: SensitiveString;
}

export const DEFAULT_HD_ACCOUNTS_CONFIG_PARAMS: DefaultHDAccountsConfigParams =
{
initialIndex: 0,
count: 20,
path: "m/44'/60'/0'/0",
passphrase: "",
};

export function derivePrivateKeys(
mnemonic: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ import {
DEFAULT_EDR_NETWORK_HD_ACCOUNTS_CONFIG_PARAMS,
EDR_NETWORK_DEFAULT_COINBASE,
} from "./edr/edr-provider.js";
import { HardforkName } from "./edr/types/hardfork.js";
import { isHdAccountsConfig } from "./type-validation.js";
import { HardforkName, LATEST_HARDFORK } from "./edr/types/hardfork.js";
import { isHdAccountsUserConfig } from "./type-validation.js";

export function resolveGasConfig(value: GasUserConfig = "auto"): GasConfig {
return value === "auto" ? value : BigInt(value);
Expand All @@ -48,10 +48,16 @@ export function resolveHttpNetworkAccounts(
});
}

if (isHdAccountsConfig(accounts)) {
if (isHdAccountsUserConfig(accounts)) {
const { passphrase: defaultPassphrase, ...defaultHdAccountRest } =
DEFAULT_HD_ACCOUNTS_CONFIG_PARAMS;
const { mnemonic, passphrase, ...hdAccountRest } = accounts;

return {
...DEFAULT_HD_ACCOUNTS_CONFIG_PARAMS,
...accounts,
...defaultHdAccountRest,
...hdAccountRest,
mnemonic: resolveConfigurationVariable(mnemonic),
passphrase: resolveConfigurationVariable(passphrase ?? defaultPassphrase),
};
}

Expand All @@ -62,27 +68,41 @@ export function resolveEdrNetworkAccounts(
accounts:
| EdrNetworkAccountsUserConfig
| undefined = DEFAULT_EDR_NETWORK_HD_ACCOUNTS_CONFIG_PARAMS,
resolveConfigurationVariable: ConfigurationResolver,
): EdrNetworkAccountsConfig {
if (Array.isArray(accounts)) {
return accounts.map(({ privateKey, balance }) => ({
privateKey: normalizeHexString(privateKey),
balance: BigInt(balance),
}));
return accounts.map(({ privateKey, balance }) => {
if (typeof privateKey === "string") {
privateKey = normalizeHexString(privateKey);
}

return {
privateKey: resolveConfigurationVariable(privateKey),
balance: BigInt(balance),
};
});
}

const {
mnemonic: defaultMnemonic,
accountsBalance: defaultAccountsBalance,
passphrase: defaultPassphrase,
...defaultHdAccountRest
} = DEFAULT_EDR_NETWORK_HD_ACCOUNTS_CONFIG_PARAMS;
const { mnemonic, passphrase, accountsBalance, ...hdAccountRest } = accounts;
return {
...DEFAULT_EDR_NETWORK_HD_ACCOUNTS_CONFIG_PARAMS,
...accounts,
accountsBalance: BigInt(
accounts.accountsBalance ??
DEFAULT_EDR_NETWORK_HD_ACCOUNTS_CONFIG_PARAMS.accountsBalance,
),
...defaultHdAccountRest,
...hdAccountRest,
mnemonic: resolveConfigurationVariable(mnemonic ?? defaultMnemonic),
accountsBalance: BigInt(accountsBalance ?? defaultAccountsBalance),
passphrase: resolveConfigurationVariable(passphrase ?? defaultPassphrase),
};
}

export function resolveForkingConfig(
forkingUserConfig: EdrNetworkForkingUserConfig | undefined,
cacheDir: string,
resolveConfigurationVariable: ConfigurationResolver,
): EdrNetworkForkingConfig | undefined {
if (forkingUserConfig === undefined) {
return undefined;
Expand All @@ -98,7 +118,7 @@ export function resolveForkingConfig(

return {
enabled: forkingUserConfig.enabled ?? true,
url: forkingUserConfig.url,
url: resolveConfigurationVariable(forkingUserConfig.url),
cacheDir: path.join(cacheDir, "edr-fork-cache"),
blockNumber:
forkingUserConfig.blockNumber !== undefined
Expand Down Expand Up @@ -274,7 +294,7 @@ export function resolveHardfork(
}

if (enableTransientStorage === true) {
return HardforkName.CANCUN;
return LATEST_HARDFORK;
} else {
return HardforkName.SHANGHAI;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import type { SolidityStackTrace } from "./stack-traces/solidity-stack-trace.js";
import type { LoggerConfig } from "./types/logger.js";
import type { TracingConfig } from "./types/node-types.js";
import type {
EdrNetworkConfig,
EdrNetworkHDAccountsConfig,
} from "../../../../types/config.js";
import type { EdrNetworkConfig } from "../../../../types/config.js";
import type {
EthereumProvider,
EthSubscription,
Expand All @@ -18,6 +15,7 @@ import type {
CompilerInput,
CompilerOutput,
} from "../../../../types/solidity/compiler-io.js";
import type { DefaultHDAccountsConfigParams } from "../accounts/derive-private-keys.js";
import type { JsonRpcRequestWrapperFunction } from "../network-manager.js";
import type {
RawTrace,
Expand Down Expand Up @@ -81,10 +79,16 @@ const log = debug("hardhat:core:hardhat-network:provider");
export const EDR_NETWORK_DEFAULT_COINBASE =
"0xc014ba5ec014ba5ec014ba5ec014ba5ec014ba5e";

interface EdrNetworkDefaultHDAccountsConfigParams
extends DefaultHDAccountsConfigParams {
mnemonic: string;
accountsBalance: bigint;
}

export const EDR_NETWORK_MNEMONIC =
"test test test test test test test test test test test junk";
export const DEFAULT_EDR_NETWORK_BALANCE = 10000000000000000000000n;
export const DEFAULT_EDR_NETWORK_HD_ACCOUNTS_CONFIG_PARAMS: EdrNetworkHDAccountsConfig =
export const DEFAULT_EDR_NETWORK_HD_ACCOUNTS_CONFIG_PARAMS: EdrNetworkDefaultHDAccountsConfigParams =
{
...DEFAULT_HD_ACCOUNTS_CONFIG_PARAMS,
mnemonic: EDR_NETWORK_MNEMONIC,
Expand Down Expand Up @@ -141,12 +145,14 @@ export class EdrProvider extends EventEmitter implements EthereumProvider {

const vmTraceDecoder = await createVmTraceDecoder();

const providerConfig = await getProviderConfig(networkConfig);

const context = await getGlobalEdrContext();
const provider = await context.createProvider(
networkConfig.chainType === "optimism"
? OPTIMISM_CHAIN_TYPE
: GENERIC_CHAIN_TYPE, // TODO: l1 is missing here
getProviderConfig(networkConfig),
providerConfig,
{
enable: loggerConfig.enabled,
decodeConsoleLogInputsCallback: ConsoleLogger.getDecodedLogs,
Expand Down Expand Up @@ -537,7 +543,9 @@ export class EdrProvider extends EventEmitter implements EthereumProvider {
}
}

function getProviderConfig(networkConfig: EdrNetworkConfig): ProviderConfig {
async function getProviderConfig(
networkConfig: EdrNetworkConfig,
): Promise<ProviderConfig> {
return {
allowBlocksWithSameTimestamp: networkConfig.allowBlocksWithSameTimestamp,
allowUnlimitedContractSize: networkConfig.allowUnlimitedContractSize,
Expand All @@ -550,8 +558,8 @@ function getProviderConfig(networkConfig: EdrNetworkConfig): ProviderConfig {
// TODO: remove this cast when EDR updates the interface to accept Uint8Array
coinbase: Buffer.from(networkConfig.coinbase),
enableRip7212: networkConfig.enableRip7212,
fork: hardhatForkingConfigToEdrForkConfig(networkConfig.forking),
genesisAccounts: hardhatAccountsToEdrGenesisAccounts(
fork: await hardhatForkingConfigToEdrForkConfig(networkConfig.forking),
genesisAccounts: await hardhatAccountsToEdrGenesisAccounts(
networkConfig.accounts,
),
hardfork: hardhatHardforkToEdrSpecId(networkConfig.hardfork),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,22 @@ export enum HardforkName {
SHANGHAI = "shanghai",
CANCUN = "cancun",
}

const HARDFORK_ORDER = Object.values(HardforkName);

export const LATEST_HARDFORK: HardforkName =
HARDFORK_ORDER[HARDFORK_ORDER.length - 1];

/**
* Check if `hardforkA` is greater than or equal to `hardforkB`,
* that is, if it includes all its changes.
*/
export function hardforkGte(
hardforkA: HardforkName,
hardforkB: HardforkName,
): boolean {
const indexA = HARDFORK_ORDER.lastIndexOf(hardforkA);
const indexB = HARDFORK_ORDER.lastIndexOf(hardforkB);

return indexA >= indexB;
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
} from "@ignored/edr-optimism";
import { bytesToHexString } from "@ignored/hardhat-vnext-utils/bytes";

import { FixedValueConfigurationVariable } from "../../../../core/configuration-variables.js";
import { derivePrivateKeys } from "../../accounts/derive-private-keys.js";
import { DEFAULT_EDR_NETWORK_BALANCE } from "../edr-provider.js";
import { HardforkName } from "../types/hardfork.js";
Expand Down Expand Up @@ -220,34 +221,34 @@ export function edrRpcDebugTraceToHardhat(
};
}

export function hardhatAccountsToEdrGenesisAccounts(
export async function hardhatAccountsToEdrGenesisAccounts(
accounts: EdrNetworkAccountsConfig,
): GenesisAccount[] {
const normalizedAccounts = normalizeEdrNetworkAccountsConfig(accounts);
): Promise<GenesisAccount[]> {
const normalizedAccounts = await normalizeEdrNetworkAccountsConfig(accounts);

return normalizedAccounts.map((account) => {
return {
secretKey: account.privateKey,
balance: account.balance,
};
});
const accountPromises = normalizedAccounts.map(async (account) => ({
secretKey: await account.privateKey.getHexString(),
balance: account.balance,
}));

return Promise.all(accountPromises);
}

function normalizeEdrNetworkAccountsConfig(
async function normalizeEdrNetworkAccountsConfig(
accounts: EdrNetworkAccountsConfig,
): EdrNetworkAccountConfig[] {
): Promise<EdrNetworkAccountConfig[]> {
if (Array.isArray(accounts)) {
return accounts;
}

return derivePrivateKeys(
accounts.mnemonic,
await accounts.mnemonic.get(),
accounts.path,
accounts.initialIndex,
accounts.count,
accounts.passphrase,
await accounts.passphrase.get(),
).map((pk) => ({
privateKey: bytesToHexString(pk),
privateKey: new FixedValueConfigurationVariable(bytesToHexString(pk)),
balance: accounts.accountsBalance ?? DEFAULT_EDR_NETWORK_BALANCE,
}));
}
Expand Down Expand Up @@ -278,13 +279,13 @@ export function hardhatChainsToEdrChains(
return edrChains;
}

export function hardhatForkingConfigToEdrForkConfig(
export async function hardhatForkingConfigToEdrForkConfig(
forkingConfig: EdrNetworkForkingConfig | undefined,
): ForkConfig | undefined {
): Promise<ForkConfig | undefined> {
let fork: ForkConfig | undefined;
if (forkingConfig !== undefined && forkingConfig.enabled === true) {
fork = {
jsonRpcUrl: forkingConfig.url,
jsonRpcUrl: await forkingConfig.url.getUrl(),
blockNumber: forkingConfig.blockNumber,
httpHeaders: forkingConfig.httpHeaders,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ import {
resolveInitialBaseFeePerGas,
resolveMiningConfig,
} from "../config-resolution.js";
import { validateUserConfig } from "../type-validation.js";
import { validateNetworkUserConfig } from "../type-validation.js";

export default async (): Promise<Partial<ConfigHooks>> => ({
extendUserConfig,
validateUserConfig,
validateUserConfig: validateNetworkUserConfig,
resolveUserConfig,
});

Expand Down Expand Up @@ -113,7 +113,7 @@ export async function resolveUserConfig(
gas: resolveGasConfig(networkConfig.gas),
gasMultiplier: networkConfig.gasMultiplier ?? 1,
gasPrice: resolveGasConfig(networkConfig.gasPrice),
url: networkConfig.url,
url: resolveConfigurationVariable(networkConfig.url),
timeout: networkConfig.timeout ?? 20_000,
httpHeaders: networkConfig.httpHeaders ?? {},
};
Expand All @@ -124,7 +124,10 @@ export async function resolveUserConfig(
if (networkConfig.type === "edr") {
const resolvedNetworkConfig: EdrNetworkConfig = {
type: "edr",
accounts: resolveEdrNetworkAccounts(networkConfig.accounts),
accounts: resolveEdrNetworkAccounts(
networkConfig.accounts,
resolveConfigurationVariable,
),
chainId: networkConfig.chainId ?? 31337,
chainType: networkConfig.chainType,
from: networkConfig.from,
Expand All @@ -144,6 +147,7 @@ export async function resolveUserConfig(
forking: resolveForkingConfig(
networkConfig.forking,
resolvedConfig.paths.cache,
resolveConfigurationVariable,
),
hardfork: resolveHardfork(
networkConfig.hardfork,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export class NetworkManagerImplementation implements NetworkManager {
}

return HttpProvider.create({
url: resolvedNetworkConfig.url,
url: await resolvedNetworkConfig.url.getUrl(),
networkName: resolvedNetworkName,
extraHeaders: resolvedNetworkConfig.httpHeaders,
timeout: resolvedNetworkConfig.timeout,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,11 @@ export async function createHandlersArray<
requestHandlers.push(
new HDWalletHandler(
networkConnection.provider,
accounts.mnemonic,
await accounts.mnemonic.get(),
accounts.path,
accounts.initialIndex,
accounts.count,
accounts.passphrase,
await accounts.passphrase.get(),
),
);
}
Expand Down
Loading

0 comments on commit c8adb6d

Please sign in to comment.