trufflesuite/truffle

View on GitHub
packages/from-hardhat/src/api.ts

Summary

Maintainability
A
0 mins
Test Coverage
import { promises as fs } from "fs";
import semver from "semver";

import type * as Hardhat from "hardhat/types";
import type * as Common from "@truffle/compile-common";
import type TruffleConfig from "@truffle/config";

import {
  supportedHardhatVersionRange,
  supportedHardhatBuildInfoFormats
} from "./constants";
import * as Compilation from "./compilation";
import * as Config from "./config";

import {
  checkHardhat,
  askHardhatConsole,
  askHardhatVersion
} from "./ask-hardhat";
import { EnvironmentOptions } from "./options";

/**
 * Checks for the existence of a Hardhat project configuration and asserts
 * that the local installed version of Hardhat matches this package's
 * supported version range.
 *
 * @param options to control process environment (e.g. working directory)
 * @return Promise<void> when expectation holds
 * @throws NotHardhatError when not in a Hardhat project directory
 * @throws IncompatibleHardhatError if Hardhat has unsupported version
 */
export const expectHardhat = async (
  options?: EnvironmentOptions
): Promise<void> => {
  const isHardhat = await checkHardhat(options);

  if (!isHardhat) {
    throw new NotHardhatError();
  }

  const hardhatVersion = await askHardhatVersion(options);

  if (!semver.satisfies(hardhatVersion, supportedHardhatVersionRange)) {
    throw new IncompatibleHardhatVersionError(hardhatVersion);
  }
};

/**
 * Thrown when no Hardhat project is found
 */
export class NotHardhatError extends Error {
  constructor() {
    super("Current working directory is not part of a Hardhat project");
  }
}

/**
 * Thrown when Hardhat was detected but with an incompatible version
 */
export class IncompatibleHardhatVersionError extends Error {
  constructor(detectedVersion: string) {
    super(
      `Expected Hardhat version compatible with ${supportedHardhatVersionRange}, got: ${detectedVersion}`
    );
  }
}

/**
 * Constructs a @truffle/config object based on the Hardhat config.
 *
 * WARNING: except for fields documented here, the values present on the
 * returned @truffle/config object MUST be regarded as unsafe to use.
 *
 * The returned `config` is defined to contain the following:
 *
 *   - `config.networks` with configurations for all Hardhat-configured
 *     networks, provided:
 *       - The configured network is not the built-in `hardhat` network
 *       - The configured network defines a `url` property
 *
 *     Note: this function ignores all properties other than `url`,
 *     including any information that can be used for computing
 *     cryptographic signatures. THIS FUNCTION DOES NOT READ PRIVATE KEYS.
 *
 * Suffice to say:
 *
 * THIS FUNCTION'S BEHAVIOR IS EXPERIMENTAL AND SHOULD ONLY BE USED IN
 * SPECIFICALLY KNOWN-SUPPORTED USE CASES (like reading for configured
 * network urls)
 *
 * @param options to control process environment (e.g. working directory)
 * @return Promise<TruffleConfig>
 *
 * @dev This function shells out to `npx hardhat console` to ask the Hardhat
 *      runtime environment for the Hardhat config info that Truffle needs.
 */
export const prepareConfig = async (
  options?: EnvironmentOptions
): Promise<TruffleConfig> => {
  // define a function to grab only the networks and fields we need.
  //
  // this duplicates the transformation behavior in src/config.ts for safe
  // measure, since these two components may differ in requirements in the
  // future.
  const extractNetworks = (hre: Hardhat.HardhatRuntimeEnvironment) => ({
    networks: Object.entries(hre.config.networks)
      .filter(
        (
          pair
        ): pair is [string, Hardhat.HardhatNetworkConfig & { url: string }] =>
          pair[0] !== "hardhat" && "url" in pair[1]
      )
      .map(([networkName, { url }]) => ({
        [networkName]: { url }
      }))
      .reduce((a, b) => ({ ...a, ...b }), {})
  });

  const hardhatConfig = (await askHardhatConsole(
    // stringify the function hooray
    `(${extractNetworks.toString()})(hre)`,
    options
  )) as Hardhat.HardhatConfig;

  return Config.fromHardhatConfig(hardhatConfig);
};

/**
 * Constructs an array of @truffle/compile-common `Compilation` objects
 * corresponding one-to-one with Hardhat's persisted results of each solc
 * compilation.
 *
 * WARNING: this function only supports Hardhat projects written entirely
 * in solc-compatible languages (Solidity, Yul). Behavior of this function
 * for Hardhat projects using other languages is undefined.
 *
 * @param options to control process environment (e.g. working directory)
 * @return Promise<Compilation[]> from @truffle/compile-common
 *
 * @dev This function shells out to `npx hardhat console` to ask the Hardhat
 *      runtime environment for the location of the project build info
 *      files
 */
export const prepareCompilations = async (
  options?: EnvironmentOptions
): Promise<Common.Compilation[]> => {
  const compilations = [];

  const buildInfoPaths = (await askHardhatConsole(
    `artifacts.getBuildInfoPaths()`,
    options
  )) as string[];

  for (const buildInfoPath of buildInfoPaths) {
    const buildInfo: Hardhat.BuildInfo = JSON.parse(
      (await fs.readFile(buildInfoPath)).toString()
    );

    const { _format } = buildInfo;

    if (!supportedHardhatBuildInfoFormats.has(_format)) {
      throw new IncompatibleHardhatBuildInfoFormatError(_format);
    }

    const compilation = Compilation.fromBuildInfo(buildInfo);

    compilations.push(compilation);
  }

  return compilations;
};

/**
 * Thrown when the build-info format detected has an incompatible version
 */
export class IncompatibleHardhatBuildInfoFormatError extends Error {
  constructor(detectedFormat: string) {
    super(
      `Expected build-info to be one of ["${[
        ...supportedHardhatBuildInfoFormats
      ].join('", "')}"], got: "${detectedFormat}"`
    );
  }
}