text: "use strict";
  var __importDefault = (this && this.__importDefault) || function (mod) {
      return (mod && mod.__esModule) ? mod : { "default": mod };
  };
  Object.defineProperty(exports, "__esModule", { value: true });
  const ansi_escapes_1 = __importDefault(require("ansi-escapes"));
  const chalk_1 = __importDefault(require("chalk"));
  const debug_1 = __importDefault(require("debug"));
  const events_1 = require("events");
  const fs_extra_1 = __importDefault(require("fs-extra"));
  const path_1 = __importDefault(require("path"));
  const semver_1 = __importDefault(require("semver"));
  const util_1 = __importDefault(require("util"));
  const constants_1 = require("../../constants");
  const solidity_errors_1 = require("../stack-traces/solidity-errors");
  const solidityTracer_1 = require("../stack-traces/solidityTracer");
  const await_semaphore_1 = require("../vendor/await-semaphore");
  const errors_1 = require("./errors");
  const buidler_1 = require("./modules/buidler");
  const eth_1 = require("./modules/eth");
  const evm_1 = require("./modules/evm");
  const logger_1 = require("./modules/logger");
  const net_1 = require("./modules/net");
  const web3_1 = require("./modules/web3");
  const node_1 = require("./node");
  const log = debug_1.default("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
  class BuidlerEVMProvider extends events_1.EventEmitter {
      constructor(_hardfork, _networkName, _chainId, _networkId, _blockGasLimit, _throwOnTransactionFailures, _throwOnCallFailures, _genesisAccounts = [], _solcVersion, _paths, _loggingEnabled = false, _allowUnlimitedContractSize = false, _initialDate, _experimentalBuidlerEVMMessageTraceHooks = []) {
          super();
          this._hardfork = _hardfork;
          this._networkName = _networkName;
          this._chainId = _chainId;
          this._networkId = _networkId;
          this._blockGasLimit = _blockGasLimit;
          this._throwOnTransactionFailures = _throwOnTransactionFailures;
          this._throwOnCallFailures = _throwOnCallFailures;
          this._genesisAccounts = _genesisAccounts;
          this._solcVersion = _solcVersion;
          this._paths = _paths;
          this._loggingEnabled = _loggingEnabled;
          this._allowUnlimitedContractSize = _allowUnlimitedContractSize;
          this._initialDate = _initialDate;
          this._experimentalBuidlerEVMMessageTraceHooks = _experimentalBuidlerEVMMessageTraceHooks;
          this._mutex = new await_semaphore_1.Mutex();
          this._logger = new logger_1.ModulesLogger();
          this._methodCollapsedCount = 0;
      }
      async send(method, params = []) {
          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();
          }
      }
      async _sendWithLogging(method, params = []) {
          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_1.default.green);
              }
              const loggedSomething = this._logModuleMessages();
              if (loggedSomething) {
                  this._stopCollapsingMethod();
                  this._log("");
              }
              return result;
          }
          catch (err) {
              this._stopCollapsingMethod();
              if (err instanceof errors_1.MethodNotFoundError ||
                  err instanceof errors_1.MethodNotSupportedError) {
                  this._log(`${method} - Method not supported`, false, chalk_1.default.red);
                  throw err;
              }
              this._log(method, false, chalk_1.default.red);
              const loggedSomething = this._logModuleMessages();
              if (loggedSomething) {
                  this._log("");
              }
              if (err instanceof solidity_errors_1.SolidityError) {
                  this._logError(err);
              }
              else if (err instanceof errors_1.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;
          }
      }
      _logCollapsedMethod(method) {
          this._methodCollapsedCount += 1;
          process.stdout.write(
          // tslint:disable-next-line:prefer-template
          ansi_escapes_1.default.cursorHide +
              ansi_escapes_1.default.cursorPrevLine +
              chalk_1.default.green(`${method} (${this._methodCollapsedCount})`) +
              "\n" +
              ansi_escapes_1.default.eraseEndLine +
              ansi_escapes_1.default.cursorShow);
      }
      _startCollapsingMethod(method) {
          this._methodBeingCollapsed = method;
          this._methodCollapsedCount = 1;
      }
      _stopCollapsingMethod() {
          this._methodBeingCollapsed = undefined;
          this._methodCollapsedCount = 0;
      }
      _shouldCollapseMethod(method) {
          return (method === this._methodBeingCollapsed &&
              !this._logger.hasLogs() &&
              this._methodCollapsedCount > 0);
      }
      async _send(method, params = []) {
          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 errors_1.MethodNotFoundError(`Method ${method} not found`);
      }
      async _init() {
          if (this._node !== undefined) {
              return;
          }
          let compilerInput;
          let compilerOutput;
          if (this._solcVersion !== undefined && this._paths !== undefined) {
              if (semver_1.default.lt(this._solcVersion, solidityTracer_1.FIRST_SOLC_VERSION_SUPPORTED)) {
                  console.warn(chalk_1.default.yellow(`Solidity stack traces only work with Solidity version ${solidityTracer_1.FIRST_SOLC_VERSION_SUPPORTED} or higher.`));
              }
              else {
                  let hasCompiledContracts = false;
                  if (await fs_extra_1.default.pathExists(this._paths.artifacts)) {
                      const artifactsDir = await fs_extra_1.default.readdir(this._paths.artifacts);
                      hasCompiledContracts = artifactsDir.some((f) => f.endsWith(".json"));
                  }
                  if (hasCompiledContracts) {
                      try {
                          const solcInputPath = path_1.default.join(this._paths.cache, constants_1.SOLC_INPUT_FILENAME);
                          const solcOutputPath = path_1.default.join(this._paths.cache, constants_1.SOLC_OUTPUT_FILENAME);
                          compilerInput = await fs_extra_1.default.readJSON(solcInputPath, {
                              encoding: "utf8",
                          });
                          compilerOutput = await fs_extra_1.default.readJSON(solcOutputPath, {
                              encoding: "utf8",
                          });
                      }
                      catch (error) {
                          console.warn(chalk_1.default.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 node_1.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 eth_1.EthModule(common, node, this._throwOnTransactionFailures, this._throwOnCallFailures, this._loggingEnabled ? this._logger : undefined, this._experimentalBuidlerEVMMessageTraceHooks);
          this._netModule = new net_1.NetModule(common);
          this._web3Module = new web3_1.Web3Module();
          this._evmModule = new evm_1.EvmModule(node);
          this._buidlerModule = new buidler_1.BuidlerModule(node);
          const listener = (payload) => {
              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);
      }
      _logModuleMessages() {
          const logs = this._logger.getLogs();
          if (logs.length === 0) {
              return false;
          }
          for (const msg of logs) {
              this._log(msg, true);
          }
          return true;
      }
      _logError(err, logInRed = false) {
          this._log(util_1.default.inspect(err), true, logInRed ? chalk_1.default.red : undefined);
      }
      _log(msg, indent = false, color) {
          if (indent) {
              msg = msg
                  .split("\n")
                  .map((line) => `  ${line}`)
                  .join("\n");
          }
          if (color !== undefined) {
              console.log(color(msg));
              return;
          }
          console.log(msg);
      }
  }
  exports.BuidlerEVMProvider = BuidlerEVMProvider;
  //# sourceMappingURL=provider.js.map