trufflesuite/truffle

View on GitHub
packages/core/lib/networks.js

Summary

Maintainability
C
1 day
Test Coverage
const fs = require("fs");
const path = require("path");
const OS = require("os");
const BlockchainUtils = require("@truffle/blockchain-utils");
const Provider = require("@truffle/provider");
const { createInterfaceAdapter } = require("@truffle/interface-adapter");

const Networks = {
  deployed: async function(options) {
    let files;
    try {
      // Only read JSON files in directory
      files = fs
        .readdirSync(options.contracts_build_directory)
        .filter(fn => fn.endsWith(".json"));
    } catch (error) {
      // We can't read the directory. Act like we found nothing.
      files = [];
    }

    const binaries = files.map(file => {
      const filePath = path.join(options.contracts_build_directory, file);
      const fileContents = fs.readFileSync(filePath, "utf8");
      return JSON.parse(fileContents);
    });

    const idsToNames = {};
    const networks = {};

    for (let networkName in options.networks) {
      const network = options.networks[networkName];
      const networkId = network.network_id;

      if (networkId == null) return;

      idsToNames[networkId] = networkName;
      networks[networkName] = {};
    }

    for (let json of binaries) {
      for (let networkId in json.networks) {
        const networkName = idsToNames[networkId] || networkId;

        if (networks[networkName] == null) networks[networkName] = {};

        const address = json.networks[networkId].address;

        if (address == null) return;

        networks[networkName][json.contractName] = address;
      }
    }
    return networks;
  },

  display: async function(config) {
    const networks = await this.deployed(config);
    const { networkNames, starNetworks } = Object.keys(networks)
      .sort()
      .reduce(
        (acc, networkName) => {
          if (
            config.networks[networkName] &&
            config.networks[networkName].network_id === "*"
          ) {
            acc.starNetworks.push(networkName);
          } else {
            acc.networkNames.push(networkName);
          }
          return acc;
        },
        { networkNames: [], starNetworks: [] }
      );

    const unknownNetworks = networkNames.filter(networkName => {
      const configuredNetworks = Object.keys(config.networks);
      let found = false;
      for (let i = 0; i < configuredNetworks.length; i++) {
        const configuredNetworkName = configuredNetworks[i];
        if (networkName === configuredNetworkName) {
          found = true;
          break;
        }
      }

      return !found;
    });

    // Only display this warning if:
    //
    //   At least one network is configured with the wildcard ('*') network id
    //   There's a least one network deployed to
    //   And one of those networks deployed to is unknown (i.e., unconfigured).
    if (
      starNetworks.length > 0 &&
      networkNames.length > 0 &&
      unknownNetworks.length > 0
    ) {
      config.logger.log(
        OS.EOL +
          "The following networks are configured to match any network id ('*'):" +
          OS.EOL
      );

      starNetworks.forEach(networkName => {
        config.logger.log("    " + networkName);
      });

      config.logger.log(
        OS.EOL +
          "Closely inspect the deployed networks below, and use `truffle networks --clean` to remove any networks that don't match your configuration. You should not use the wildcard configuration ('*') for staging and production networks for which you intend to deploy your application."
      );
    }

    networkNames.forEach(networkName => {
      config.logger.log("");

      let output = Object.keys(networks[networkName])
        .sort()
        .map(contract_name => {
          const address = networks[networkName][contract_name];
          return contract_name + ": " + address;
        });

      if (output.length === 0) output = ["No contracts deployed."];

      let message = "Network: ";

      const is_id = config.networks[networkName] == null;

      if (is_id) {
        message += "UNKNOWN (id: " + networkName + ")";
      } else {
        message +=
          networkName +
          " (id: " +
          config.networks[networkName].network_id +
          ")";
      }

      config.logger.log(message);
      config.logger.log("  " + output.join("\n  "));
    });

    if (networkNames.length === 0) {
      config.logger.log(
        OS.EOL + "Contracts have not been deployed to any network."
      );
    }
    config.logger.log("");
  },

  clean: async function(config) {
    // Only read JSON files in directory
    let files = fs
      .readdirSync(config.contracts_build_directory)
      .filter(fn => fn.endsWith(".json"));
    const configuredNetworks = Object.keys(config.networks);
    const results = [];

    files.forEach(file => {
      const filePath = path.join(config.contracts_build_directory, file);
      const fileContents = fs.readFileSync(filePath, "utf8");
      const body = JSON.parse(fileContents);

      for (let installedNetworkId of Object.keys(body.networks)) {
        let found = false;
        for (let i = 0; i < configuredNetworks.length; i++) {
          const configuredNetwork = configuredNetworks[i];

          // If an installed network id matches a configured id, then we can ignore this one.
          let parsedNetworkId;
          try {
            // Account for an integer or string in the config
            parsedNetworkId = parseInt(installedNetworkId);
          } catch (_error) {
            // If it can't be parsed into an int like * then don't worry about it
          }
          if (
            installedNetworkId ===
              config.networks[configuredNetwork].network_id ||
            parsedNetworkId === config.networks[configuredNetwork].network_id
          ) {
            found = true;
            break;
          }
        }
        // If we didn't find a suitable configuration, delete this network.
        if (found === false) delete body.networks[installedNetworkId];
      }
      // Our work is done here. Save the file.
      fs.writeFileSync(filePath, JSON.stringify(body, null, 2), "utf8");
      results.push(body);
    });

    // TODO: Display what's removed?
    return results;
  },

  // Try to connect to every named network except for "test" and "development"
  asURIs: async function(options, networks) {
    const result = {
      uris: {},
      failed: []
    };

    for (const networkName of networks) {
      const provider = Provider.create(options.networks[networkName]);
      try {
        const uri = await BlockchainUtils.asURI(provider);
        result.uris[networkName] = uri;
      } catch (error) {
        result.failed.push(networkName);
      }
    }

    return result;
  },

  matchesNetwork: async function(network_id, network_options) {
    const provider = Provider.create(network_options);

    const first = network_id + "";
    const second = network_options.network_id + "";

    if (first === second) return true;

    const isFirstANumber = isNaN(parseInt(network_id)) === false;
    const isSecondANumber =
      isNaN(parseInt(network_options.network_id)) === false;

    // If both network ids are numbers, then they don't match, and we should quit.
    if (isFirstANumber && isSecondANumber) return false;

    const interfaceAdapter = createInterfaceAdapter({
      provider,
      networkType: network_options.type
    });

    const currentNetworkID = await interfaceAdapter.getNetworkId();
    if (first === currentNetworkID) return true;
    if (isFirstANumber === false)
      await BlockchainUtils.matches(first, provider);
    else {
      // Nothing else to compare.
      return false;
    }
  }
};

module.exports = Networks;