hardhat
Version:
Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.
246 lines • 13.6 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 { 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 { HttpProvider } from "./http-provider.js";
import { NetworkConnectionImplementation } from "./network-connection.js";
export class NetworkManagerImplementation {
#defaultNetwork;
#defaultChainType;
#networkConfigs;
#hookManager;
#artifactsManager;
#userConfig;
#chainDescriptors;
#userProvidedConfigPath;
#projectRoot;
#nextConnectionId = 0;
constructor(defaultNetwork, defaultChainType, networkConfigs, hookManager, artifactsManager, userConfig, chainDescriptors, userProvidedConfigPath, projectRoot) {
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;
}
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 = "default", _hostname, port) {
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]);
}
},
};
}
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,
tracingConfig: {
buildInfos: await this.#getBuildInfosAndOutputsAsBuffers(),
ignoreContracts: false,
},
coverageConfig,
gasReportConfig,
});
}
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];
if (Object.keys(networkConfigOverride).length === 0 &&
resolvedChainType === existingNetworkConfig.chainType) {
return existingNetworkConfig;
}
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) {
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;
}
}
//# sourceMappingURL=network-manager.js.map