UNPKG

9.57 kBPlain TextView Raw
1import debug from "debug";
2
3import {
4 BuidlerArguments,
5 BuidlerRuntimeEnvironment,
6 EnvironmentExtender,
7 EthereumProvider,
8 ExperimentalBuidlerEVMMessageTraceHook,
9 Network,
10 ParamDefinition,
11 ResolvedBuidlerConfig,
12 RunSuperFunction,
13 RunTaskFunction,
14 TaskArguments,
15 TaskDefinition,
16 TasksMap,
17} from "../../types";
18import { MessageTrace } from "../buidler-evm/stack-traces/message-trace";
19import { lazyObject } from "../util/lazy";
20
21import { BuidlerError } from "./errors";
22import { ERRORS } from "./errors-list";
23import { createProvider } from "./providers/construction";
24import { OverriddenTaskDefinition } from "./tasks/task-definitions";
25
26const log = debug("buidler:core:bre");
27
28export class Environment implements BuidlerRuntimeEnvironment {
29 private static readonly _BLACKLISTED_PROPERTIES: string[] = [
30 "injectToGlobal",
31 "_runTaskDefinition",
32 ];
33
34 /**
35 * An EIP1193 Ethereum provider.
36 */
37 public ethereum: EthereumProvider;
38
39 public network: Network;
40
41 private readonly _extenders: EnvironmentExtender[];
42
43 /**
44 * Initializes the Buidler Runtime Environment and the given
45 * extender functions.
46 *
47 * @remarks The extenders' execution order is given by the order
48 * of the requires in the buidler's config file and its plugins.
49 *
50 * @param config The buidler's config object.
51 * @param buidlerArguments The parsed buidler's arguments.
52 * @param tasks A map of tasks.
53 * @param extenders A list of extenders.
54 */
55 constructor(
56 public readonly config: ResolvedBuidlerConfig,
57 public readonly buidlerArguments: BuidlerArguments,
58 public readonly tasks: TasksMap,
59 extenders: EnvironmentExtender[] = [],
60 experimentalBuidlerEVMMessageTraceHooks: ExperimentalBuidlerEVMMessageTraceHook[] = []
61 ) {
62 log("Creating BuidlerRuntimeEnvironment");
63
64 const networkName =
65 buidlerArguments.network !== undefined
66 ? buidlerArguments.network
67 : config.defaultNetwork;
68
69 const networkConfig = config.networks[networkName];
70
71 if (networkConfig === undefined) {
72 throw new BuidlerError(ERRORS.NETWORK.CONFIG_NOT_FOUND, {
73 network: networkName,
74 });
75 }
76
77 const provider = lazyObject(() => {
78 log(`Creating provider for network ${networkName}`);
79 return createProvider(
80 networkName,
81 networkConfig,
82 config.solc.version,
83 config.paths,
84 experimentalBuidlerEVMMessageTraceHooks.map(
85 (hook) => (trace: MessageTrace, isCallMessageTrace: boolean) =>
86 hook(this, trace, isCallMessageTrace)
87 )
88 );
89 });
90
91 this.network = {
92 name: networkName,
93 config: config.networks[networkName],
94 provider,
95 };
96
97 this.ethereum = provider;
98 this._extenders = extenders;
99
100 extenders.forEach((extender) => extender(this));
101 }
102
103 /**
104 * Executes the task with the given name.
105 *
106 * @param name The task's name.
107 * @param taskArguments A map of task's arguments.
108 *
109 * @throws a BDLR303 if there aren't any defined tasks with the given name.
110 * @returns a promise with the task's execution result.
111 */
112 public readonly run: RunTaskFunction = async (name, taskArguments = {}) => {
113 const taskDefinition = this.tasks[name];
114
115 log("Running task %s", name);
116
117 if (taskDefinition === undefined) {
118 throw new BuidlerError(ERRORS.ARGUMENTS.UNRECOGNIZED_TASK, {
119 task: name,
120 });
121 }
122
123 const resolvedTaskArguments = this._resolveValidTaskArguments(
124 taskDefinition,
125 taskArguments
126 );
127
128 return this._runTaskDefinition(taskDefinition, resolvedTaskArguments);
129 };
130
131 /**
132 * Injects the properties of `this` (the Buidler Runtime Environment) into the global scope.
133 *
134 * @param blacklist a list of property names that won't be injected.
135 *
136 * @returns a function that restores the previous environment.
137 */
138 public injectToGlobal(
139 blacklist: string[] = Environment._BLACKLISTED_PROPERTIES
140 ): () => void {
141 const globalAsAny = global as any;
142
143 const previousValues: { [name: string]: any } = {};
144
145 for (const [key, value] of Object.entries(this)) {
146 if (blacklist.includes(key)) {
147 continue;
148 }
149
150 previousValues[key] = globalAsAny[key];
151 globalAsAny[key] = value;
152 }
153
154 return () => {
155 for (const [key, _] of Object.entries(this)) {
156 if (blacklist.includes(key)) {
157 continue;
158 }
159
160 globalAsAny[key] = previousValues[key];
161 }
162 };
163 }
164
165 private async _runTaskDefinition(
166 taskDefinition: TaskDefinition,
167 taskArguments: TaskArguments
168 ) {
169 let runSuperFunction: any;
170
171 if (taskDefinition instanceof OverriddenTaskDefinition) {
172 runSuperFunction = async (
173 _taskArguments: TaskArguments = taskArguments
174 ) => {
175 log("Running %s's super", taskDefinition.name);
176
177 return this._runTaskDefinition(
178 taskDefinition.parentTaskDefinition,
179 _taskArguments
180 );
181 };
182
183 runSuperFunction.isDefined = true;
184 } else {
185 runSuperFunction = async () => {
186 throw new BuidlerError(ERRORS.TASK_DEFINITIONS.RUNSUPER_NOT_AVAILABLE, {
187 taskName: taskDefinition.name,
188 });
189 };
190
191 runSuperFunction.isDefined = false;
192 }
193
194 const runSuper: RunSuperFunction<TaskArguments> = runSuperFunction;
195
196 const globalAsAny = global as any;
197 const previousRunSuper: any = globalAsAny.runSuper;
198 globalAsAny.runSuper = runSuper;
199
200 const uninjectFromGlobal = this.injectToGlobal();
201
202 try {
203 return await taskDefinition.action(taskArguments, this, runSuper);
204 } finally {
205 uninjectFromGlobal();
206 globalAsAny.runSuper = previousRunSuper;
207 }
208 }
209
210 /**
211 * Check that task arguments are within TaskDefinition defined params constraints.
212 * Also, populate missing, non-mandatory arguments with default param values (if any).
213 *
214 * @private
215 * @throws BuidlerError if any of the following are true:
216 * > a required argument is missing
217 * > an argument's value's type doesn't match the defined param type
218 *
219 * @param taskDefinition
220 * @param taskArguments
221 * @returns resolvedTaskArguments
222 */
223 private _resolveValidTaskArguments(
224 taskDefinition: TaskDefinition,
225 taskArguments: TaskArguments
226 ): TaskArguments {
227 const { paramDefinitions, positionalParamDefinitions } = taskDefinition;
228
229 const nonPositionalParamDefinitions = Object.values(paramDefinitions);
230
231 // gather all task param definitions
232 const allTaskParamDefinitions = [
233 ...nonPositionalParamDefinitions,
234 ...positionalParamDefinitions,
235 ];
236
237 const initResolvedArguments: {
238 errors: BuidlerError[];
239 values: TaskArguments;
240 } = { errors: [], values: {} };
241
242 const resolvedArguments = allTaskParamDefinitions.reduce(
243 ({ errors, values }, paramDefinition) => {
244 try {
245 const paramName = paramDefinition.name;
246 const argumentValue = taskArguments[paramName];
247 const resolvedArgumentValue = this._resolveArgument(
248 paramDefinition,
249 argumentValue
250 );
251 if (resolvedArgumentValue !== undefined) {
252 values[paramName] = resolvedArgumentValue;
253 }
254 } catch (error) {
255 errors.push(error);
256 }
257 return { errors, values };
258 },
259 initResolvedArguments
260 );
261
262 const { errors: resolveErrors, values: resolvedValues } = resolvedArguments;
263
264 // if has argument errors, throw the first one
265 if (resolveErrors.length > 0) {
266 throw resolveErrors[0];
267 }
268
269 // append the rest of arguments that where not in the task param definitions
270 const resolvedTaskArguments = { ...taskArguments, ...resolvedValues };
271
272 return resolvedTaskArguments;
273 }
274
275 /**
276 * Resolves an argument according to a ParamDefinition rules.
277 *
278 * @param paramDefinition
279 * @param argumentValue
280 * @private
281 */
282 private _resolveArgument(
283 paramDefinition: ParamDefinition<any>,
284 argumentValue: any
285 ) {
286 const { name, isOptional, defaultValue, type } = paramDefinition;
287
288 if (argumentValue === undefined) {
289 if (isOptional) {
290 // undefined & optional argument -> return defaultValue
291 return defaultValue;
292 }
293
294 // undefined & mandatory argument -> error
295 throw new BuidlerError(ERRORS.ARGUMENTS.MISSING_TASK_ARGUMENT, {
296 param: name,
297 });
298 }
299
300 // arg was present -> validate type, if applicable
301 this._checkTypeValidation(paramDefinition, argumentValue);
302
303 return argumentValue;
304 }
305
306 /**
307 * Checks if value is valid for the specified param definition.
308 *
309 * @param paramDefinition {ParamDefinition} - the param definition for validation
310 * @param argumentValue - the value to be validated
311 * @private
312 * @throws BDLR301 if value is not valid for the param type
313 */
314 private _checkTypeValidation(
315 paramDefinition: ParamDefinition<any>,
316 argumentValue: any
317 ) {
318 const { name: paramName, type, isVariadic } = paramDefinition;
319 if (type === undefined || type.validate === undefined) {
320 // no type or no validate() method defined, just skip validation.
321 return;
322 }
323
324 // in case of variadic param, argValue is an array and the type validation must pass for all values.
325 // otherwise, it's a single value that is to be validated
326 const argumentValueContainer = isVariadic ? argumentValue : [argumentValue];
327
328 for (const value of argumentValueContainer) {
329 type.validate(paramName, value);
330 }
331 }
332}