hardhat
Version:
Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.
430 lines (398 loc) • 12.7 kB
text/typescript
/* eslint-disable no-restricted-syntax -- hack */
import type {
EdrNetworkAccountConfig,
EdrNetworkAccountsConfig,
ChainDescriptorsConfig,
EdrNetworkForkingConfig,
EdrNetworkMempoolConfig,
EdrNetworkMiningConfig,
} from "../../../../../types/config.js";
import type { ChainType } from "../../../../../types/network.js";
import type { GasMeasurement } from "../../../gas-analytics/types.js";
import type {
IntervalRange,
ChainOverride,
ForkConfig,
GasReport,
} from "@nomicfoundation/edr";
import {
GasReportExecutionStatus,
MineOrdering,
OpHardfork,
SpecId,
FRONTIER,
HOMESTEAD,
DAO_FORK,
TANGERINE,
SPURIOUS_DRAGON,
BYZANTIUM,
CONSTANTINOPLE,
PETERSBURG,
ISTANBUL,
MUIR_GLACIER,
BERLIN,
LONDON,
ARROW_GLACIER,
GRAY_GLACIER,
MERGE,
SHANGHAI,
CANCUN,
PRAGUE,
OSAKA,
BEDROCK,
REGOLITH,
CANYON,
ECOTONE,
FJORD,
GRANITE,
HOLOCENE,
ISTHMUS,
} from "@nomicfoundation/edr";
import {
GENERIC_CHAIN_TYPE,
L1_CHAIN_TYPE,
OPTIMISM_CHAIN_TYPE,
} from "../../../../constants.js";
import { FixedValueConfigurationVariable } from "../../../../core/configuration-variables.js";
import { derivePrivateKeys } from "../../accounts/derive-private-keys.js";
import {
DEFAULT_EDR_NETWORK_BALANCE,
EDR_NETWORK_DEFAULT_PRIVATE_KEYS,
isDefaultEdrNetworkHDAccountsConfig,
} from "../edr-provider.js";
import { L1HardforkName, OpHardforkName } from "../types/hardfork.js";
import { getL1HardforkName, getOpHardforkName } from "./hardfork.js";
export function edrL1HardforkToHardhatL1HardforkName(
hardfork: SpecId,
): L1HardforkName {
switch (hardfork) {
case SpecId.Frontier:
return L1HardforkName.FRONTIER;
case SpecId.FrontierThawing:
return L1HardforkName.FRONTIER;
case SpecId.Homestead:
return L1HardforkName.HOMESTEAD;
case SpecId.DaoFork:
return L1HardforkName.DAO;
case SpecId.Tangerine:
return L1HardforkName.TANGERINE_WHISTLE;
case SpecId.SpuriousDragon:
return L1HardforkName.SPURIOUS_DRAGON;
case SpecId.Byzantium:
return L1HardforkName.BYZANTIUM;
case SpecId.Constantinople:
return L1HardforkName.CONSTANTINOPLE;
case SpecId.Petersburg:
return L1HardforkName.PETERSBURG;
case SpecId.Istanbul:
return L1HardforkName.ISTANBUL;
case SpecId.MuirGlacier:
return L1HardforkName.MUIR_GLACIER;
case SpecId.Berlin:
return L1HardforkName.BERLIN;
case SpecId.London:
return L1HardforkName.LONDON;
case SpecId.ArrowGlacier:
return L1HardforkName.ARROW_GLACIER;
case SpecId.GrayGlacier:
return L1HardforkName.GRAY_GLACIER;
case SpecId.Merge:
return L1HardforkName.MERGE;
case SpecId.Shanghai:
return L1HardforkName.SHANGHAI;
case SpecId.Cancun:
return L1HardforkName.CANCUN;
case SpecId.Prague:
return L1HardforkName.PRAGUE;
case SpecId.Osaka:
return L1HardforkName.OSAKA;
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- trust but verify
default:
const _exhaustiveCheck: never = hardfork;
throw new Error(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- we want to print the fork
`Unknown L1 hardfork '${hardfork as SpecId}', this shouldn't happen`,
);
}
}
export function edrOpHardforkToHardhatOpHardforkName(
hardfork: OpHardfork,
): OpHardforkName {
switch (hardfork) {
case OpHardfork.Bedrock:
return OpHardforkName.BEDROCK;
case OpHardfork.Regolith:
return OpHardforkName.REGOLITH;
case OpHardfork.Canyon:
return OpHardforkName.CANYON;
case OpHardfork.Ecotone:
return OpHardforkName.ECOTONE;
case OpHardfork.Fjord:
return OpHardforkName.FJORD;
case OpHardfork.Granite:
return OpHardforkName.GRANITE;
case OpHardfork.Holocene:
return OpHardforkName.HOLOCENE;
case OpHardfork.Isthmus:
return OpHardforkName.ISTHMUS;
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- trust but verify
default:
const _exhaustiveCheck: never = hardfork;
throw new Error(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- we want to print the fork
`Unknown OP hardfork '${hardfork as OpHardfork}', this shouldn't happen`,
);
}
}
export function hardhatHardforkToEdrSpecId(
hardfork: string,
chainType: ChainType,
): string {
return chainType === OPTIMISM_CHAIN_TYPE
? hardhatOpHardforkToEdrSpecId(hardfork)
: hardhatL1HardforkToEdrSpecId(hardfork);
}
function hardhatOpHardforkToEdrSpecId(hardfork: string): string {
const hardforkName = getOpHardforkName(hardfork);
switch (hardforkName) {
case OpHardforkName.BEDROCK:
return BEDROCK;
case OpHardforkName.REGOLITH:
return REGOLITH;
case OpHardforkName.CANYON:
return CANYON;
case OpHardforkName.ECOTONE:
return ECOTONE;
case OpHardforkName.FJORD:
return FJORD;
case OpHardforkName.GRANITE:
return GRANITE;
case OpHardforkName.HOLOCENE:
return HOLOCENE;
case OpHardforkName.ISTHMUS:
return ISTHMUS;
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- trust but verify
default:
const _exhaustiveCheck: never = hardforkName;
throw new Error(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- we want to print the fork
`Unknown hardfork name '${hardforkName as string}', this shouldn't happen`,
);
}
}
function hardhatL1HardforkToEdrSpecId(hardfork: string): string {
const hardforkName = getL1HardforkName(hardfork);
switch (hardforkName) {
case L1HardforkName.FRONTIER:
return FRONTIER;
case L1HardforkName.HOMESTEAD:
return HOMESTEAD;
case L1HardforkName.DAO:
return DAO_FORK;
case L1HardforkName.TANGERINE_WHISTLE:
return TANGERINE;
case L1HardforkName.SPURIOUS_DRAGON:
return SPURIOUS_DRAGON;
case L1HardforkName.BYZANTIUM:
return BYZANTIUM;
case L1HardforkName.CONSTANTINOPLE:
return CONSTANTINOPLE;
case L1HardforkName.PETERSBURG:
return PETERSBURG;
case L1HardforkName.ISTANBUL:
return ISTANBUL;
case L1HardforkName.MUIR_GLACIER:
return MUIR_GLACIER;
case L1HardforkName.BERLIN:
return BERLIN;
case L1HardforkName.LONDON:
return LONDON;
case L1HardforkName.ARROW_GLACIER:
return ARROW_GLACIER;
case L1HardforkName.GRAY_GLACIER:
return GRAY_GLACIER;
case L1HardforkName.MERGE:
return MERGE;
case L1HardforkName.SHANGHAI:
return SHANGHAI;
case L1HardforkName.CANCUN:
return CANCUN;
case L1HardforkName.PRAGUE:
return PRAGUE;
case L1HardforkName.OSAKA:
return OSAKA;
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- we want to print the fork
default:
const _exhaustiveCheck: never = hardforkName;
throw new Error(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- an enum can be safely cast to a string
`Unknown hardfork name '${hardfork as string}', this shouldn't happen`,
);
}
}
export function hardhatMiningIntervalToEdrMiningInterval(
config: EdrNetworkMiningConfig["interval"],
): bigint | IntervalRange | undefined {
if (typeof config === "number") {
// Is interval mining disabled?
if (config === 0) {
return undefined;
} else {
return BigInt(config);
}
} else {
return {
min: BigInt(config[0]),
max: BigInt(config[1]),
};
}
}
export function hardhatMempoolOrderToEdrMineOrdering(
mempoolOrder: EdrNetworkMempoolConfig["order"],
): MineOrdering {
switch (mempoolOrder) {
case "fifo":
return MineOrdering.Fifo;
case "priority":
return MineOrdering.Priority;
}
}
export async function hardhatAccountsToEdrOwnedAccounts(
accounts: EdrNetworkAccountsConfig,
): Promise<Array<{ secretKey: string; balance: bigint }>> {
const normalizedAccounts = await normalizeEdrNetworkAccountsConfig(accounts);
const accountPromises = normalizedAccounts.map(async (account) => ({
secretKey: await account.privateKey.getHexString(),
balance: account.balance,
}));
return Promise.all(accountPromises);
}
export async function normalizeEdrNetworkAccountsConfig(
accounts: EdrNetworkAccountsConfig,
): Promise<EdrNetworkAccountConfig[]> {
if (Array.isArray(accounts)) {
return accounts;
}
const isDefaultConfig = await isDefaultEdrNetworkHDAccountsConfig(accounts);
const derivedPrivateKeys = isDefaultConfig
? EDR_NETWORK_DEFAULT_PRIVATE_KEYS
: await derivePrivateKeys(
await accounts.mnemonic.get(),
accounts.path,
accounts.initialIndex,
accounts.count,
await accounts.passphrase.get(),
);
return derivedPrivateKeys.map((privateKey) => ({
privateKey: new FixedValueConfigurationVariable(privateKey),
balance: accounts.accountsBalance ?? DEFAULT_EDR_NETWORK_BALANCE,
}));
}
export function hardhatChainDescriptorsToEdrChainOverrides(
chainDescriptors: ChainDescriptorsConfig,
chainType: ChainType,
): ChainOverride[] {
return (
Array.from(chainDescriptors)
// Skip chain descriptors that don't match the expected chain type
.filter(([_, descriptor]) => {
if (chainType === GENERIC_CHAIN_TYPE) {
// When "generic" is requested, include both "generic" and "l1" chains
return (
descriptor.chainType === GENERIC_CHAIN_TYPE ||
descriptor.chainType === L1_CHAIN_TYPE
);
}
return descriptor.chainType === chainType;
})
.map(([chainId, descriptor]) => {
const chainOverride: ChainOverride = {
chainId,
name: descriptor.name,
};
if (descriptor.hardforkHistory !== undefined) {
chainOverride.hardforkActivationOverrides = Array.from(
descriptor.hardforkHistory,
).map(([hardfork, { blockNumber, timestamp }]) => ({
condition:
blockNumber !== undefined
? { blockNumber: BigInt(blockNumber) }
: { timestamp: BigInt(timestamp) },
hardfork: hardhatHardforkToEdrSpecId(
hardfork,
descriptor.chainType,
),
}));
}
return chainOverride;
})
);
}
export async function hardhatForkingConfigToEdrForkConfig(
forkingConfig: EdrNetworkForkingConfig | undefined,
chainDescriptors: ChainDescriptorsConfig,
chainType: ChainType,
): Promise<ForkConfig | undefined> {
let fork: ForkConfig | undefined;
if (forkingConfig !== undefined && forkingConfig.enabled === true) {
const httpHeaders =
forkingConfig.httpHeaders !== undefined
? Object.entries(forkingConfig.httpHeaders).map(([name, value]) => ({
name,
value,
}))
: undefined;
fork = {
blockNumber: forkingConfig.blockNumber,
cacheDir: forkingConfig.cacheDir,
chainOverrides: hardhatChainDescriptorsToEdrChainOverrides(
chainDescriptors,
chainType,
),
httpHeaders,
url: await forkingConfig.url.getUrl(),
};
}
return fork;
}
/**
* Converts EDR's nested GasReport structure into a flat array of gas entries.
* Filters out reverted transactions.
*/
export function edrGasReportToHardhatGasMeasurements(
gasReport: GasReport,
excludedContractFqns: string[] = [],
): GasMeasurement[] {
const gasMeasurements: GasMeasurement[] = [];
for (const [contractFqn, data] of Object.entries(gasReport.contracts)) {
if (excludedContractFqns.includes(contractFqn)) {
continue;
}
// Process deployments
for (const deployment of data.deployments) {
if (deployment.status === GasReportExecutionStatus.Success) {
gasMeasurements.push({
contractFqn,
type: "deployment",
gas: Number(deployment.gas),
runtimeSize: Number(deployment.runtimeSize),
});
}
}
// Process function calls
for (const [functionSig, calls] of Object.entries(data.functions)) {
for (const call of calls) {
if (call.status === GasReportExecutionStatus.Success) {
gasMeasurements.push({
contractFqn,
type: "function",
functionSig,
gas: Number(call.gas),
proxyChain: call.proxyChain,
});
}
}
}
}
return gasMeasurements;
}