UNPKG

hardhat

Version:

Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.

476 lines (408 loc) 14 kB
import type { MessageTrace } from "../hardhat-network/stack-traces/message-trace"; import debug from "debug"; import { Artifacts as IArtifacts, EnvironmentExtender, ExperimentalHardhatNetworkMessageTraceHook, HardhatArguments, HardhatConfig, HardhatRuntimeEnvironment, HardhatUserConfig, Network, ParamDefinition, ProviderExtender, RunSuperFunction, RunTaskFunction, SubtaskArguments, TaskArguments, TaskDefinition, TasksMap, ScopesMap, } from "../../types"; import { Artifacts } from "../artifacts"; import { getHardhatVersion } from "../util/packageInfo"; import { analyzeModuleNotFoundError } from "./config/config-loading"; import { HardhatError } from "./errors"; import { ERRORS } from "./errors-list"; import { createProvider } from "./providers/construction"; import { LazyInitializationProviderAdapter } from "./providers/lazy-initialization"; import { OverriddenTaskDefinition } from "./tasks/task-definitions"; import { completeTaskProfile, createParentTaskProfile, createTaskProfile, TaskProfile, } from "./task-profiling"; import { parseTaskIdentifier } from "./tasks/util"; const log = debug("hardhat:core:hre"); export class Environment implements HardhatRuntimeEnvironment { private static readonly _BLACKLISTED_PROPERTIES: string[] = [ "injectToGlobal", "entryTaskProfile", "_runTaskDefinition", "_extenders", ]; public network: Network; public artifacts: IArtifacts; private readonly _environmentExtenders: EnvironmentExtender[]; public entryTaskProfile?: TaskProfile; public version: string = getHardhatVersion(); /** * Initializes the Hardhat Runtime Environment and the given * extender functions. * * @remarks The extenders' execution order is given by the order * of the requires in the hardhat's config file and its plugins. * * @param config The hardhat's config object. * @param hardhatArguments The parsed hardhat's arguments. * @param tasks A map of tasks. * @param scopes A map of scopes. * @param environmentExtenders A list of environment extenders. * @param providerExtenders A list of provider extenders. */ constructor( public readonly config: HardhatConfig, public readonly hardhatArguments: HardhatArguments, public readonly tasks: TasksMap, public readonly scopes: ScopesMap, environmentExtenders: EnvironmentExtender[] = [], experimentalHardhatNetworkMessageTraceHooks: ExperimentalHardhatNetworkMessageTraceHook[] = [], public readonly userConfig: HardhatUserConfig = {}, providerExtenders: ProviderExtender[] = [] ) { log("Creating HardhatRuntimeEnvironment"); const networkName = hardhatArguments.network !== undefined ? hardhatArguments.network : config.defaultNetwork; const networkConfig = config.networks[networkName]; if (networkConfig === undefined) { throw new HardhatError(ERRORS.NETWORK.CONFIG_NOT_FOUND, { network: networkName, }); } this.artifacts = new Artifacts(config.paths.artifacts); const provider = new LazyInitializationProviderAdapter(async () => { log(`Creating provider for network ${networkName}`); return createProvider( config, networkName, this.artifacts, experimentalHardhatNetworkMessageTraceHooks.map( (hook) => (trace: MessageTrace, isCallMessageTrace: boolean) => hook(this, trace, isCallMessageTrace) ), providerExtenders ); }); this.network = { name: networkName, config: networkConfig, provider, }; this._environmentExtenders = environmentExtenders; environmentExtenders.forEach((extender) => extender(this)); } /** * Executes the task with the given name. * * @param taskIdentifier The task or scoped task to be executed. * @param taskArguments A map of task's arguments. * @param subtaskArguments A map of subtasks to their arguments. * * @throws a HH303 if there aren't any defined tasks with the given name. * @returns a promise with the task's execution result. */ public readonly run: RunTaskFunction = async ( taskIdentifier, taskArguments = {}, subtaskArguments = {}, callerTaskProfile?: TaskProfile ) => { const { scope, task } = parseTaskIdentifier(taskIdentifier); let taskDefinition; if (scope === undefined) { taskDefinition = this.tasks[task]; log("Running task %s", task); } else { const scopeDefinition = this.scopes[scope]; if (scopeDefinition === undefined) { throw new HardhatError(ERRORS.ARGUMENTS.UNRECOGNIZED_SCOPE, { scope, }); } taskDefinition = scopeDefinition.tasks?.[task]; log("Running scoped task %s %s", scope, task); } if (taskDefinition === undefined) { if (scope !== undefined) { throw new HardhatError(ERRORS.ARGUMENTS.UNRECOGNIZED_SCOPED_TASK, { scope, task, }); } throw new HardhatError(ERRORS.ARGUMENTS.UNRECOGNIZED_TASK, { task, }); } const resolvedTaskArguments = this._resolveValidTaskArguments( taskDefinition, taskArguments, subtaskArguments ); let taskProfile: TaskProfile | undefined; if (this.hardhatArguments.flamegraph === true) { taskProfile = createTaskProfile(task); if (callerTaskProfile !== undefined) { callerTaskProfile.children.push(taskProfile); } else { this.entryTaskProfile = taskProfile; } } try { return await this._runTaskDefinition( taskDefinition, resolvedTaskArguments, subtaskArguments, taskProfile ); } catch (e) { analyzeModuleNotFoundError(e, this.config.paths.configFile); // eslint-disable-next-line @nomicfoundation/hardhat-internal-rules/only-hardhat-error throw e; } finally { if (taskProfile !== undefined) { completeTaskProfile(taskProfile); } } }; /** * Injects the properties of `this` (the Hardhat Runtime Environment) into the global scope. * * @param blacklist a list of property names that won't be injected. * * @returns a function that restores the previous environment. */ public injectToGlobal( blacklist: string[] = Environment._BLACKLISTED_PROPERTIES ): () => void { const globalAsAny = global as any; const previousValues: { [name: string]: any } = {}; const previousHre = globalAsAny.hre; globalAsAny.hre = this; for (const [key, value] of Object.entries(this)) { if (blacklist.includes(key)) { continue; } previousValues[key] = globalAsAny[key]; globalAsAny[key] = value; } return () => { for (const [key, _] of Object.entries(this)) { if (blacklist.includes(key)) { continue; } globalAsAny.hre = previousHre; globalAsAny[key] = previousValues[key]; } }; } /** * @param taskProfile Undefined if we aren't computing task profiles * @private */ private async _runTaskDefinition( taskDefinition: TaskDefinition, taskArguments: TaskArguments, subtaskArguments: SubtaskArguments, taskProfile?: TaskProfile ): Promise<any> { let runSuperFunction: any; if (taskDefinition instanceof OverriddenTaskDefinition) { runSuperFunction = async ( _taskArguments: TaskArguments = taskArguments, _subtaskArguments: SubtaskArguments = subtaskArguments ) => { log("Running %s's super", taskDefinition.name); if (taskProfile === undefined) { return this._runTaskDefinition( taskDefinition.parentTaskDefinition, _taskArguments, _subtaskArguments ); } const parentTaskProfile = createParentTaskProfile(taskProfile); taskProfile.children.push(parentTaskProfile); try { return await this._runTaskDefinition( taskDefinition.parentTaskDefinition, _taskArguments, _subtaskArguments, parentTaskProfile ); } finally { completeTaskProfile(parentTaskProfile); } }; runSuperFunction.isDefined = true; } else { runSuperFunction = async () => { throw new HardhatError(ERRORS.TASK_DEFINITIONS.RUNSUPER_NOT_AVAILABLE, { taskName: taskDefinition.name, }); }; runSuperFunction.isDefined = false; } const runSuper: RunSuperFunction<TaskArguments> = runSuperFunction; const globalAsAny = global as any; const previousRunSuper: any = globalAsAny.runSuper; globalAsAny.runSuper = runSuper; // We create a proxied version of `this`, as we want to keep track of the // `subtaskArguments` and `taskProfile` through `run` invocations. This // way we keep track of callers's data, even when tasks are run in parallel. const proxiedHre = new Proxy<Environment>(this, { get(target: Environment, p: string | symbol, receiver: any): any { if (p === "run") { return ( _name: string, _taskArguments: TaskArguments, _subtaskArguments: SubtaskArguments ) => (target as any).run( _name, _taskArguments, { ..._subtaskArguments, ...subtaskArguments }, // parent subtask args take precedence taskProfile ); } return Reflect.get(target, p, receiver); }, }); if (this.hardhatArguments.flamegraph === true) { // We modify the `this` again to add a few utility methods. (proxiedHre as any).adhocProfile = async ( _name: string, f: () => Promise<any> ) => { const adhocProfile = createTaskProfile(_name); taskProfile!.children.push(adhocProfile); try { return await f(); } finally { completeTaskProfile(adhocProfile); } }; (proxiedHre as any).adhocProfileSync = (_name: string, f: () => any) => { const adhocProfile = createTaskProfile(_name); taskProfile!.children.push(adhocProfile); try { return f(); } finally { completeTaskProfile(adhocProfile); } }; } const uninjectFromGlobal = proxiedHre.injectToGlobal(); try { return await taskDefinition.action(taskArguments, proxiedHre, runSuper); } finally { uninjectFromGlobal(); globalAsAny.runSuper = previousRunSuper; } } /** * Check that task arguments are within TaskDefinition defined params constraints. * Also, populate missing, non-mandatory arguments with default param values (if any). * * @private * @throws HardhatError if any of the following are true: * > a required argument is missing * > an argument's value's type doesn't match the defined param type * * @param taskDefinition * @param taskArguments * @returns resolvedTaskArguments */ private _resolveValidTaskArguments( taskDefinition: TaskDefinition, taskArguments: TaskArguments, subtaskArguments: SubtaskArguments ): TaskArguments { const { name: taskName, paramDefinitions, positionalParamDefinitions, } = taskDefinition; const nonPositionalParamDefinitions = Object.values(paramDefinitions); // gather all task param definitions const allTaskParamDefinitions = [ ...nonPositionalParamDefinitions, ...positionalParamDefinitions, ]; const resolvedArguments: TaskArguments = {}; for (const paramDefinition of allTaskParamDefinitions) { const paramName = paramDefinition.name; const argumentValue = subtaskArguments[taskName]?.[paramName] ?? taskArguments[paramName]; const resolvedArgumentValue = this._resolveArgument( paramDefinition, argumentValue, taskDefinition.name ); if (resolvedArgumentValue !== undefined) { resolvedArguments[paramName] = resolvedArgumentValue; } } // We keep the args in taskArguments that were not resolved return { ...taskArguments, ...resolvedArguments }; } /** * Resolves an argument according to a ParamDefinition rules. * * @param paramDefinition * @param argumentValue * @private */ private _resolveArgument( paramDefinition: ParamDefinition<any>, argumentValue: any, taskName: string ) { const { name, isOptional, defaultValue } = paramDefinition; if (argumentValue === undefined) { if (isOptional) { // undefined & optional argument -> return defaultValue return defaultValue; } // undefined & mandatory argument -> error throw new HardhatError(ERRORS.ARGUMENTS.MISSING_TASK_ARGUMENT, { param: name, task: taskName, }); } // arg was present -> validate type, if applicable this._checkTypeValidation(paramDefinition, argumentValue); return argumentValue; } /** * Checks if value is valid for the specified param definition. * * @param paramDefinition {ParamDefinition} - the param definition for validation * @param argumentValue - the value to be validated * @private * @throws HH301 if value is not valid for the param type */ private _checkTypeValidation( paramDefinition: ParamDefinition<any>, argumentValue: any ) { const { name: paramName, type, isVariadic } = paramDefinition; // in case of variadic param, argValue is an array and the type validation must pass for all values. // otherwise, it's a single value that is to be validated const argumentValueContainer = isVariadic ? argumentValue : [argumentValue]; for (const value of argumentValueContainer) { type.validate(paramName, value); } } }