hardhat
Version:
Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.
316 lines • 17.7 kB
JavaScript
import { HardhatError, assertHardhatInvariant, } from "@nomicfoundation/hardhat-errors";
import { exists, readBinaryFile } from "@nomicfoundation/hardhat-utils/fs";
import { deepMerge } from "@nomicfoundation/hardhat-utils/lang";
import { AsyncMutex } from "@nomicfoundation/hardhat-utils/synchronization";
import { resolveUserConfigToHardhatConfig } from "../../core/hre.js";
import { isSupportedChainType } from "../../edr/chain-type.js";
import { JsonRpcServerImplementation } from "../node/json-rpc/server.js";
import { EdrProvider } from "./edr/edr-provider.js";
import { getHardforks } from "./edr/types/hardfork.js";
import { edrGasReportToHardhatGasMeasurements } from "./edr/utils/convert-to-edr.js";
import { verbosityToIncludeTraces } from "./edr/utils/trace-formatters.js";
import { HttpProvider } from "./http-provider.js";
import { NetworkConnectionImplementation } from "./network-connection.js";
export class NetworkManagerImplementation {
#defaultNetwork;
#defaultChainType;
#networkConfigs;
#hookManager;
#artifactsManager;
#userConfig;
#chainDescriptors;
#userProvidedConfigPath;
#projectRoot;
#verbosity;
#nextConnectionId = 0;
#contractDecoderMutex = new AsyncMutex();
#contractDecoder;
constructor(defaultNetwork, defaultChainType, networkConfigs, hookManager, artifactsManager, userConfig, chainDescriptors, userProvidedConfigPath, projectRoot, verbosity) {
this.#defaultNetwork = defaultNetwork;
this.#defaultChainType = defaultChainType;
this.#networkConfigs = networkConfigs;
this.#hookManager = hookManager;
this.#artifactsManager = artifactsManager;
this.#userConfig = userConfig;
this.#chainDescriptors = chainDescriptors;
this.#userProvidedConfigPath = userProvidedConfigPath;
this.#projectRoot = projectRoot;
this.#verbosity = verbosity;
}
async connect(networkOrParams) {
let networkName;
let chainType;
let override;
if (typeof networkOrParams === "string") {
networkName = networkOrParams;
}
else if (networkOrParams !== undefined) {
networkName = networkOrParams.network;
chainType = networkOrParams.chainType;
override = networkOrParams.override;
}
const networkConnection = await this.#hookManager.runHandlerChain("network", "newConnection", [], async (_context) => this.#initializeNetworkConnection(networkName, chainType, override));
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
-- Cast to NetworkConnection<ChainTypeT> because we know it's valid */
return networkConnection;
}
async createServer(networkOrParams, _hostname, port) {
this.#ensureNetworkOrParamsIsNotHttpNetworkConfig(networkOrParams);
const insideDocker = await exists("/.dockerenv");
const hostname = _hostname ?? (insideDocker ? "0.0.0.0" : "127.0.0.1");
const { provider } = await this.connect(networkOrParams);
return new JsonRpcServerImplementation({
hostname,
port,
provider,
});
}
async #initializeNetworkConnection(networkName, chainType, networkConfigOverride) {
const resolvedNetworkName = networkName ?? this.#defaultNetwork;
const existingNetworkConfig = this.#networkConfigs[resolvedNetworkName];
if (existingNetworkConfig === undefined) {
throw new HardhatError(HardhatError.ERRORS.CORE.NETWORK.NETWORK_NOT_FOUND, {
networkName: resolvedNetworkName,
});
}
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions --
* Type assertion is safe: defaultChainType ensures non-undefined, and the
* resolved value will be ChainTypeT (if provided) or a fallback that
* satisfies the ChainType | string constraint */
const resolvedChainType = (chainType ??
existingNetworkConfig.chainType ??
this.#defaultChainType);
const resolvedNetworkConfig = await this.#resolveNetworkConfig(resolvedNetworkName, networkConfigOverride, resolvedChainType);
/* Capture the hook manager in a local variable to avoid retaining a
reference to the NetworkManager instance, allowing the garbage collector
to clean up the NetworkConnectionImplementation instances properly. */
const hookManager = this.#hookManager;
const createProvider = async (networkConnection) => {
const jsonRpcRequestWrapper = (request, defaultBehavior) => hookManager.runHandlerChain("network", "onRequest", [networkConnection, request], async (_context, _connection, req) => defaultBehavior(req));
if (resolvedNetworkConfig.type === "edr-simulated") {
if (!isSupportedChainType(resolvedChainType)) {
throw new HardhatError(HardhatError.ERRORS.CORE.GENERAL.UNSUPPORTED_OPERATION, { operation: `Simulating chain type ${resolvedChainType}` });
}
let coverageConfig;
const shouldEnableCoverage = await hookManager.hasHandlers("network", "onCoverageData");
if (shouldEnableCoverage) {
coverageConfig = {
onCollectedCoverageCallback: async (coverageData) => {
// NOTE: We cast the tag we receive from EDR to a hex string to
// make it easier to debug.
const tags = coverageData.map((tag) => Buffer.from(tag).toString("hex"));
await hookManager.runParallelHandlers("network", "onCoverageData", [tags]);
},
};
}
let gasReportConfig;
const shouldEnableGasStats = await hookManager.hasHandlers("network", "onGasMeasurement");
if (shouldEnableGasStats) {
gasReportConfig = {
onCollectedGasReportCallback: async (gasReport) => {
const gasMeasurements = edrGasReportToHardhatGasMeasurements(gasReport);
for (const measurement of gasMeasurements) {
await hookManager.runParallelHandlers("network", "onGasMeasurement", [measurement]);
}
},
};
}
// We load the build infos and their outputs to create a contract
// decoder when the first provider is created. Successive providers will
// reuse the same decoder as a performance optimization.
//
// The trade-off here is that if you create an EDR provider, then
// compile new contracts, and create a new provider, the new contracts
// won't be loaded.
//
// Even without this optimization, we already had the problem of new
// contracts not being visible to existing providers.
//
// In practice, most workflows compile everything before creating
// any network connection.
if (this.#contractDecoder === undefined) {
// We want to ensure that only one contract decoder is created so we
// protect the initialization with a mutex.
await this.#contractDecoderMutex.exclusiveRun(async () => {
// We check again if the decoder is undefined because another async
// execution context could have already initialized it while we were
// waiting for the mutex.
if (this.#contractDecoder === undefined) {
this.#contractDecoder = await EdrProvider.createContractDecoder({
buildInfos: await this.#getBuildInfosAndOutputsAsBuffers(),
ignoreContracts: false,
});
}
});
}
assertHardhatInvariant(this.#contractDecoder !== undefined, "Contract decoder should have been initialized before creating the provider");
const includeCallTraces = verbosityToIncludeTraces(this.#verbosity);
return EdrProvider.create({
chainDescriptors: this.#chainDescriptors,
// The resolvedNetworkConfig can have its chainType set to `undefined`
// so we default to the default chain type here.
networkConfig: {
...resolvedNetworkConfig,
// When coverage is enabled, we set allowUnlimitedContractSize to true
// because the added coverage data can push the contract size over the limit.
allowUnlimitedContractSize: shouldEnableCoverage
? true
: resolvedNetworkConfig.allowUnlimitedContractSize,
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions --
This case is safe because we have a check above */
chainType: resolvedChainType,
},
jsonRpcRequestWrapper,
contractDecoder: this.#contractDecoder,
coverageConfig,
gasReportConfig,
includeCallTraces,
connectionId: networkConnection.id,
networkName: networkConnection.networkName,
verbosity: this.#verbosity,
});
}
return HttpProvider.create({
url: await resolvedNetworkConfig.url.getUrl(),
networkName: resolvedNetworkName,
extraHeaders: resolvedNetworkConfig.httpHeaders,
timeout: resolvedNetworkConfig.timeout,
jsonRpcRequestWrapper,
});
};
return NetworkConnectionImplementation.create(this.#nextConnectionId++, resolvedNetworkName, resolvedChainType, resolvedNetworkConfig, async (connection) => {
await hookManager.runHandlerChain("network", "closeConnection", [connection], async (_context, conn) => {
await conn.provider.close();
});
}, createProvider);
}
/**
* Resolve the network connection configuration settings for the network name
* and taking into account any configuration overrides.
*
* @param resolvedNetworkName the network name for selecting the appropriate network config
* @param networkConfigOverride any network config options to override the
* defaults for the named network
* @returns a valid network configuration including any config additions from
* plugins
*/
async #resolveNetworkConfig(resolvedNetworkName, networkConfigOverride = {}, resolvedChainType) {
const existingNetworkConfig = this.#networkConfigs[resolvedNetworkName];
const hasNoOverrides = Object.keys(networkConfigOverride).length === 0;
const isChainTypeUnchanged = resolvedChainType === existingNetworkConfig.chainType;
const isChainTypeDefault = existingNetworkConfig.chainType === undefined &&
resolvedChainType === this.#defaultChainType;
if (hasNoOverrides && isChainTypeUnchanged) {
return existingNetworkConfig;
}
if (hasNoOverrides && isChainTypeDefault) {
return {
...existingNetworkConfig,
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions --
TypeScript can't follow this case, but we are just providing the
default */
chainType: resolvedChainType,
};
}
if ("type" in networkConfigOverride &&
networkConfigOverride.type !== existingNetworkConfig.type) {
throw new HardhatError(HardhatError.ERRORS.CORE.NETWORK.INVALID_CONFIG_OVERRIDE, {
errors: `\t* The type of the network cannot be changed.`,
});
}
if ("chainType" in networkConfigOverride &&
networkConfigOverride.chainType !== existingNetworkConfig.chainType) {
throw new HardhatError(HardhatError.ERRORS.CORE.NETWORK.INVALID_CONFIG_OVERRIDE, {
errors: `\t* The chainType cannot be specified in config overrides. Pass it at the top level instead: hre.network.connect({ chainType: 'op' })`,
});
}
const userConfigWithOverrides = deepMerge(this.#userConfig, {
networks: {
[resolvedNetworkName]: {
...networkConfigOverride,
chainType: resolvedChainType,
},
},
});
// This is safe, the plugins used in resolution are registered
// with the hook handler, this property is only used for
// ensuring the original plugins are available at the end
// of resolution.
const resolvedPlugins = [];
const configResolutionResult = await resolveUserConfigToHardhatConfig(userConfigWithOverrides, this.#hookManager, this.#projectRoot, this.#userProvidedConfigPath, resolvedPlugins);
if (!configResolutionResult.success) {
if (configResolutionResult.configValidationErrors !== undefined) {
throw new HardhatError(HardhatError.ERRORS.CORE.NETWORK.INVALID_CONFIG_OVERRIDE, {
errors: `\t${configResolutionResult.configValidationErrors
.map((error) => `* Error in resolved config ${error.path.join(".")}: ${error.message}`)
.join("\n\t")}`,
});
}
throw new HardhatError(HardhatError.ERRORS.CORE.NETWORK.INVALID_CONFIG_OVERRIDE, {
errors: `\t${configResolutionResult.userConfigValidationErrors
.map((error) => {
const path = this.#normaliseErrorPathToNetworkConfig(error.path, resolvedNetworkName);
let errorMessage = error.message;
// When chainType is changed but the network has a configured hardfork,
// provide a specific message explaining the hardfork must also be updated
if (path[0] === "hardfork") {
errorMessage =
`Your configured hardfork is incompatible with chainType ${resolvedChainType}. ` +
`You need to update the hardfork in your network config or pass a valid hardfork ` +
`in the overrides when connecting to the network. ` +
`Valid hardforks for chainType ${resolvedChainType} are: ` +
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
-- We know resolvedChainType is a valid ChainType */
`${getHardforks(resolvedChainType).join(", ")}.`;
}
return path.length > 0
? `* Error in ${path.join(".")}: ${errorMessage}`
: `* ${errorMessage}`;
})
.join("\n\t")}`,
});
}
const resolvedNetworkConfigOverride = configResolutionResult.config.networks[resolvedNetworkName];
assertHardhatInvariant(resolvedNetworkConfigOverride !== undefined, "The overridden network config should translate through the hook resolution of user config");
return resolvedNetworkConfigOverride;
}
async #getBuildInfosAndOutputsAsBuffers() {
const results = [];
for (const id of await this.#artifactsManager.getAllBuildInfoIds()) {
const buildInfoPath = await this.#artifactsManager.getBuildInfoPath(id);
const buildInfoOutputPath = await this.#artifactsManager.getBuildInfoOutputPath(id);
if (buildInfoPath !== undefined && buildInfoOutputPath !== undefined) {
const buildInfo = await readBinaryFile(buildInfoPath);
const output = await readBinaryFile(buildInfoOutputPath);
results.push({
buildInfo,
output,
});
}
}
return results;
}
#normaliseErrorPathToNetworkConfig(path, resolvedNetworkName) {
if (path[0] !== undefined && path[0] === "networks") {
path = path.slice(1);
}
if (path[0] !== undefined && path[0] === resolvedNetworkName) {
path = path.slice(1);
}
return path;
}
#ensureNetworkOrParamsIsNotHttpNetworkConfig(networkOrParams) {
const networkName = typeof networkOrParams === "string"
? networkOrParams
: networkOrParams?.network ?? this.#defaultNetwork;
const networkConfig = this.#networkConfigs[networkName];
if (networkConfig === undefined || networkConfig.type === "edr-simulated") {
return;
}
throw new HardhatError(HardhatError.ERRORS.CORE.NETWORK.CREATE_SERVER_UNSUPPORTED_NETWORK_TYPE, {
networkName,
networkType: networkConfig.type,
});
}
}
//# sourceMappingURL=network-manager.js.map