UNPKG

@nomiclabs/buidler

Version:

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

343 lines (286 loc) 9.95 kB
import ansiEscapes from "ansi-escapes"; import chalk, { Chalk } from "chalk"; import debug from "debug"; import Common from "ethereumjs-common"; import { BN } from "ethereumjs-util"; import { EventEmitter } from "events"; import fsExtra from "fs-extra"; import path from "path"; import semver from "semver"; import util from "util"; import { EthereumProvider, ProjectPaths } from "../../../types"; import { SOLC_INPUT_FILENAME, SOLC_OUTPUT_FILENAME } from "../../constants"; import { getUserConfigPath } from "../../core/project-structure"; import { CompilerInput, CompilerOutput } from "../stack-traces/compiler-types"; import { SolidityError } from "../stack-traces/solidity-errors"; import { FIRST_SOLC_VERSION_SUPPORTED } from "../stack-traces/solidityTracer"; import { Mutex } from "../vendor/await-semaphore"; import { BuidlerEVMProviderError, MethodNotFoundError, MethodNotSupportedError, } from "./errors"; import { BuidlerModule } from "./modules/buidler"; import { EthModule } from "./modules/eth"; import { EvmModule } from "./modules/evm"; import { ModulesLogger } from "./modules/logger"; import { NetModule } from "./modules/net"; import { Web3Module } from "./modules/web3"; import { BuidlerNode, GenesisAccount, SolidityTracerOptions } from "./node"; const log = debug("buidler:core:buidler-evm:provider"); // Set of methods that are never logged const PRIVATE_RPC_METHODS = new Set(["buidler_getStackTraceFailuresCount"]); // tslint:disable only-buidler-error export class BuidlerEVMProvider extends EventEmitter implements EthereumProvider { private _common?: Common; private _node?: BuidlerNode; private _ethModule?: EthModule; private _netModule?: NetModule; private _web3Module?: Web3Module; private _evmModule?: EvmModule; private _buidlerModule?: BuidlerModule; private readonly _mutex = new Mutex(); private readonly _logger = new ModulesLogger(); private _methodBeingCollapsed?: string; private _methodCollapsedCount: number = 0; constructor( private readonly _hardfork: string, private readonly _networkName: string, private readonly _chainId: number, private readonly _networkId: number, private readonly _blockGasLimit: number, private readonly _throwOnTransactionFailures: boolean, private readonly _throwOnCallFailures: boolean, private readonly _genesisAccounts: GenesisAccount[] = [], private readonly _solcVersion?: string, private readonly _paths?: ProjectPaths, private readonly _loggingEnabled = false, private readonly _allowUnlimitedContractSize = false, private readonly _initialDate?: Date ) { super(); const config = getUserConfigPath(); } public async send(method: string, params: any[] = []): Promise<any> { const release = await this._mutex.acquire(); try { if (this._loggingEnabled && !PRIVATE_RPC_METHODS.has(method)) { return await this._sendWithLogging(method, params); } return await this._send(method, params); } finally { release(); } } private async _sendWithLogging( method: string, params: any[] = [] ): Promise<any> { this._logger.clearLogs(); try { const result = await this._send(method, params); // We log after running the method, because we want to use different // colors depending on whether it failed or not // TODO: If an eth_call, eth_sendTransaction, or eth_sendRawTransaction // fails without throwing, this will be displayed in green. It's unclear // if this is correct. See Eth module's TODOs for more info. if (this._shouldCollapseMethod(method)) { this._logCollapsedMethod(method); } else { this._startCollapsingMethod(method); this._log(method, false, chalk.green); } const loggedSomething = this._logModuleMessages(); if (loggedSomething) { this._stopCollapsingMethod(); this._log(""); } return result; } catch (err) { this._stopCollapsingMethod(); if ( err instanceof MethodNotFoundError || err instanceof MethodNotSupportedError ) { this._log(`${method} - Method not supported`, false, chalk.red); throw err; } this._log(method, false, chalk.red); const loggedSomething = this._logModuleMessages(); if (loggedSomething) { this._log(""); } if (err instanceof SolidityError) { this._logError(err); } else if (err instanceof BuidlerEVMProviderError) { this._log(err.message, true); } else { this._logError(err, true); this._log(""); this._log( "If you think this is a bug in Buidler, please report it here: https://buidler.dev/reportbug", true ); } this._log(""); throw err; } } private _logCollapsedMethod(method: string) { this._methodCollapsedCount += 1; process.stdout.write( // tslint:disable-next-line:prefer-template ansiEscapes.cursorHide + ansiEscapes.cursorPrevLine + chalk.green(`${method} (${this._methodCollapsedCount})`) + "\n" + ansiEscapes.eraseEndLine + ansiEscapes.cursorShow ); } private _startCollapsingMethod(method: string) { this._methodBeingCollapsed = method; this._methodCollapsedCount = 1; } private _stopCollapsingMethod() { this._methodBeingCollapsed = undefined; this._methodCollapsedCount = 0; } private _shouldCollapseMethod(method: string) { return ( method === this._methodBeingCollapsed && !this._logger.hasLogs() && this._methodCollapsedCount > 0 ); } private async _send(method: string, params: any[] = []): Promise<any> { await this._init(); if (method.startsWith("eth_")) { return this._ethModule!.processRequest(method, params); } if (method.startsWith("net_")) { return this._netModule!.processRequest(method, params); } if (method.startsWith("web3_")) { return this._web3Module!.processRequest(method, params); } if (method.startsWith("evm_")) { return this._evmModule!.processRequest(method, params); } if (method.startsWith("buidler_")) { return this._buidlerModule!.processRequest(method, params); } throw new MethodNotFoundError(`Method ${method} not found`); } private async _init() { if (this._node !== undefined) { return; } let compilerInput: CompilerInput | undefined; let compilerOutput: CompilerOutput | undefined; if (this._solcVersion !== undefined && this._paths !== undefined) { if (semver.lt(this._solcVersion, FIRST_SOLC_VERSION_SUPPORTED)) { console.warn( chalk.yellow( `Solidity stack traces only work with Solidity version ${FIRST_SOLC_VERSION_SUPPORTED} or higher.` ) ); } else { let hasCompiledContracts = false; if (await fsExtra.pathExists(this._paths.artifacts)) { const artifactsDir = await fsExtra.readdir(this._paths.artifacts); hasCompiledContracts = artifactsDir.some((f) => f.endsWith(".json")); } if (hasCompiledContracts) { try { const solcInputPath = path.join( this._paths.cache, SOLC_INPUT_FILENAME ); const solcOutputPath = path.join( this._paths.cache, SOLC_OUTPUT_FILENAME ); compilerInput = await fsExtra.readJSON(solcInputPath, { encoding: "utf8", }); compilerOutput = await fsExtra.readJSON(solcOutputPath, { encoding: "utf8", }); } catch (error) { console.warn( chalk.yellow( "Stack traces engine could not be initialized. Run Buidler with --verbose to learn more." ) ); log( "Solidity stack traces disabled: Failed to read solc's input and output files. Please report this to help us improve Buidler.\n", error ); } } } } const [common, node] = await BuidlerNode.create( this._hardfork, this._networkName, this._chainId, this._networkId, this._blockGasLimit, this._genesisAccounts, this._solcVersion, this._allowUnlimitedContractSize, this._initialDate, compilerInput, compilerOutput ); this._common = common; this._node = node; this._ethModule = new EthModule( common, node, this._throwOnTransactionFailures, this._throwOnCallFailures, this._loggingEnabled ? this._logger : undefined ); this._netModule = new NetModule(common); this._web3Module = new Web3Module(); this._evmModule = new EvmModule(node); this._buidlerModule = new BuidlerModule(node); const listener = (payload: { filterId: BN; result: any }) => { this.emit("notifications", { subscription: `0x${payload.filterId.toString(16)}`, result: payload.result, }); }; // Handle eth_subscribe events and proxy them to handler this._node.addListener("ethEvent", listener); } private _logModuleMessages(): boolean { const logs = this._logger.getLogs(); if (logs.length === 0) { return false; } for (const msg of logs) { this._log(msg, true); } return true; } private _logError(err: Error, logInRed = false) { this._log(util.inspect(err), true, logInRed ? chalk.red : undefined); } private _log(msg: string, indent = false, color?: Chalk) { if (indent) { msg = msg .split("\n") .map((line) => ` ${line}`) .join("\n"); } if (color !== undefined) { console.log(color(msg)); return; } console.log(msg); } }