trufflesuite/truffle

View on GitHub
packages/events/defaultSubscribers/migrate/Messages.js

Summary

Maintainability
F
5 days
Test Coverage
/**
 *  A module that formats output for the Migrations reporter.
 */
class Messages {
  constructor(reporter) {
    this.reporter = reporter;
  }

  // ----------------------------------- Utilities -------------------------------------------------

  underline(msg) {
    return typeof msg === "number"
      ? `   ${"-".repeat(msg)}`
      : `\n   ${msg}\n   ${"-".repeat(msg.length)}`;
  }

  doubleline(msg) {
    const ul = "=".repeat(msg.length);
    return `\n${msg}\n${ul}`;
  }

  // Emoji alternative
  onMissing() {
    return "**";
  }

  migrationStatus(msg) {
    return `MIGRATION_STATUS:${JSON.stringify(msg)}`;
  }

  decAndHex(num) {
    return `${Number(num).toString(10)} (0x${Number(num).toString(16)})`;
  }

  // ----------------------------------  Errors ----------------------------------------------------

  errors(kind, data) {
    const prefix = " *** Deployment Failed ***\n\n";

    const kinds = {
      migrateErr: () =>
        `\nExiting: Review successful transactions manually by checking the transaction hashes ` +
        `above on Etherscan.\n`,

      noLibName: () => `${prefix}Cannot link a library with no name.\n`,

      noLibAddress: () =>
        `${prefix}"${data.contract.contractName}" has no address. Has it been deployed?\n`,

      noBytecode: () =>
        `${prefix}"${data.contract.contractName}" ` +
        `is an abstract contract or an interface and cannot be deployed.\n` +
        `   * Import abstractions into the '.sol' file that uses them instead of deploying them separately.\n` +
        `   * Contracts that inherit an abstraction must implement all its method signatures exactly.\n` +
        `   * A contract that only implements part of an inherited abstraction is also considered abstract.\n`,

      noBatches: () =>
        `Support for batch deployments (array syntax) is deprecated. ` +
        `Please deploy each contract individually.`,

      intWithGas: () =>
        `${prefix}"${data.contract.contractName}" ran out of gas ` +
        `(using a value you set in your network config or deployment parameters.)\n` +
        `   * Block limit:  ${this.decAndHex(data.blockLimit)}\n` +
        `   * Gas sent:     ${this.decAndHex(data.gas)}\n`,

      intNoGas: () =>
        `${prefix}"${data.contract.contractName}" ran out of gas ` +
        `(using Truffle's estimate.)\n` +
        `   * Block limit:  ${this.decAndHex(data.blockLimit)}\n` +
        `   * Gas sent:     ${this.decAndHex(data.estimate)}\n` +
        `   * Try:\n` +
        `      + Setting a higher gas estimate multiplier for this contract\n` +
        `      + Using the solc optimizer settings in 'truffle-config.js'\n` +
        `      + Making your contract smaller\n` +
        `      + Making your contract constructor more efficient\n` +
        `      + Setting a higher network block limit if you are on a\n` +
        `        private network or test client (like ganache).\n`,

      oogNoGas: () =>
        `${prefix}"${data.contract.contractName}" ran out of gas. Something in the constructor ` +
        `(ex: infinite loop) caused gas estimation to fail. Try:\n` +
        `   * Making your contract constructor more efficient\n` +
        `   * Setting the gas manually in your config or as a deployment parameter\n` +
        `   * Using the solc optimizer settings in 'truffle-config.js'\n` +
        `   * Setting a higher network block limit if you are on a\n` +
        `     private network or test client (like ganache).\n`,

      rvtReason: () =>
        `${prefix}"${data.contract.contractName}" hit a require or revert statement ` +
        `with the following reason given:\n` +
        `   * ${data.reason}\n`,

      rvtNoReason: () =>
        `${prefix}"${data.contract.contractName}" hit a require or revert statement ` +
        `somewhere in its constructor. Try:\n` +
        `   * Verifying that your constructor params satisfy all require conditions.\n` +
        `   * Adding reason strings to your require statements.\n`,

      asrtNoReason: () =>
        `${prefix}"${data.contract.contractName}" hit an invalid opcode while deploying. Try:\n` +
        `   * Verifying that your constructor params satisfy all assert conditions.\n` +
        `   * Verifying your constructor code doesn't access an array out of bounds.\n` +
        `   * Adding reason strings to your assert statements.\n`,

      noMoney: () =>
        `${prefix}"${data.contract.contractName}" could not deploy due to insufficient funds\n` +
        `   * Account:  ${data.from}\n` +
        `   * Balance:  ${data.balance} wei\n` +
        `   * Message:  ${data.error.message}\n` +
        `   * Try:\n` +
        `      + Using an adequately funded account\n` +
        `      + If you are using a local Geth node, verify that your node is synced.\n`,

      blockWithGas: () =>
        `${prefix}"${data.contract.contractName}" exceeded the block limit ` +
        `(with a gas value you set).\n` +
        `   * Block limit:  ${this.decAndHex(data.blockLimit)}\n` +
        `   * Gas sent:     ${this.decAndHex(data.gas)}\n` +
        `   * Try:\n` +
        `      + Sending less gas.\n` +
        `      + Setting a higher network block limit if you are on a\n` +
        `        private network or test client (like ganache).\n`,

      blockNoGas: () =>
        `${prefix}"${data.contract.contractName}" exceeded the block limit ` +
        `(using Truffle's estimate).\n` +
        `   * Block limit: ${this.decAndHex(data.blockLimit)}\n` +
        `   * Report this error in the Truffle issues on Github. It should not happen.\n` +
        `   * Try: setting gas manually in 'truffle-config.js' or as parameter to 'deployer.deploy'\n`,

      nonce: () =>
        `${prefix}"${data.contract.contractName}" received: ${data.error.message}.\n` +
        `   * This error is common when Infura is under heavy network load.\n` +
        `   * Try: setting the 'confirmations' key in your network config\n` +
        `          to wait for several block confirmations between each deployment.\n`,

      geth: () =>
        `${prefix}"${data.contract.contractName}" received a generic error from Geth that\n` +
        `can be caused by hitting revert in a contract constructor or running out of gas.\n` +
        `   * ${data.estimateError.message}.\n` +
        `   * Try: + using the '--dry-run' option to reproduce this failure with clearer errors.\n` +
        `          + verifying that your gas is adequate for this deployment.\n`,

      default: () =>
        `${prefix}"${data.contract.contractName}" -- ${data.error.message}.\n`
    };

    return kinds[kind]();
  }

  // ----------------------------------  Steps ----------------------------------------------------

  steps(kind, data) {
    const self = this;
    const reporter = self.reporter;
    const valueUnit = data.valueUnit || "ETH";

    const kinds = {
      // Deployments
      deploying: () => {
        let output = "";

        if (reporter.subscriber.config.describeJson) {
          output +=
            self.migrationStatus({
              status: "deploying",
              data: {
                contractName: data.contract.contractName
              }
            }) + "\n";
        }

        output += self.underline(`Deploying '${data.contract.contractName}'`);

        return output;
      },

      replacing: () => {
        let output = "";

        if (reporter.subscriber.config.describeJson) {
          output +=
            self.migrationStatus({
              status: "replacing",
              data: {
                contractName: data.contract.contractName,
                priorAddress: data.contract.address
              }
            }) + "\n";
        }

        output += self.underline(`Replacing '${data.contract.contractName}'`);

        return output;
      },

      reusing: () => {
        let output = "";

        if (reporter.subscriber.config.describeJson) {
          output +=
            self.migrationStatus({
              status: "reusing",
              data: {
                contractName: data.contract.contractName,
                address: data.contract.address
              }
            }) + "\n";
        }

        output +=
          self.underline(`Re-using deployed '${data.contract.contractName}'`) +
          "\n" +
          `   > ${"contract address:".padEnd(20)} ${data.contract.address}\n`;

        return output;
      },

      deployed: () => {
        let stopText;
        if (reporter.blockSpinner) {
          reporter.blockSpinner.remove();
          stopText = `   > ${reporter.currentBlockWait}`;
        }

        let output = "";

        if (!reporter.subscriber.config.dryRun) {
          output += `   > ${"contract address:".padEnd(20)} ${
            data.receipt.contractAddress
          }\n`;
        }

        output += `   > ${"block number:".padEnd(20)} ${
          data.receipt.blockNumber
        }\n`;

        output += `   > ${"block timestamp:".padEnd(20)} ${data.timestamp}\n`;

        output +=
          `   > ${"account:".padEnd(20)} ${data.from}\n` +
          `   > ${"balance:".padEnd(20)} ${data.balance}\n` +
          `   > ${"gas used:".padEnd(20)} ${self.decAndHex(data.gas)}\n` +
          `   > ${"gas price:".padEnd(20)} ${data.gasPrice} ${data.gasUnit}\n` +
          `   > ${"value sent:".padEnd(20)} ${data.value} ${valueUnit}\n` +
          `   > ${"total cost:".padEnd(20)} ${data.cost} ${valueUnit}\n`;

        if (
          reporter.subscriber.config.confirmations !== undefined &&
          reporter.subscriber.config.confirmations !== 0
        )
          output += self.underline(
            `Pausing for ${reporter.subscriber.config.confirmations} confirmations...\n`
          );

        if (reporter.subscriber.config.describeJson) {
          output += self.migrationStatus({
            status: "deployed",
            data: Object.assign({}, data, {
              contract: {
                contractName: data.contract.contractName,
                address: data.receipt.contractAddress
              },
              instance: undefined,
              receipt: {
                transactionHash: data.receipt.transactionHash,
                gasUsed: data.receipt.gasUsed
              }
            })
          });
        }

        return stopText ? stopText + "\n" + output : output;
      },

      // Transactions
      endTransaction: () => {
        if (reporter.blockSpinner) {
          reporter.blockSpinner.remove();
        }
        return `   > ${data.message}`;
      },

      // Libraries
      linking: () => {
        let output =
          self.underline(`Linking`) +
          `\n   * Contract: ${data.contractName} <--> Library: ${data.libraryName} `;

        if (!reporter.subscriber.config.dryRun) {
          output += `(at address: ${data.libraryAddress})`;
        }

        return output;
      },

      // PromiEvents
      hash: () =>
        `   > ${"transaction hash:".padEnd(20)} ` + data.transactionHash,

      receipt: () => `   > ${"gas usage:".padEnd(20)} ` + data.gas,

      confirmation: () =>
        `   > ${"confirmation number:".padEnd(20)} ` +
        `${data.num} (block: ${data.block})`,

      // Migrator
      preAllMigrations: () => {
        let output = "";

        if (reporter.subscriber.config.describeJson) {
          const migrations = data.migrations.map(migration =>
            migration.serializeable()
          );
          output += self.migrationStatus({
            status: "preAllMigrations",
            data: Object.assign({}, data, {
              migrations
            })
          });
        }

        return output;
      },

      postAllMigrations: () => {
        let output = "";

        if (reporter.subscriber.config.describeJson) {
          output += self.migrationStatus({
            status: "postAllMigrations",
            data
          });
        }

        return output;
      },

      // Migrations
      preMigrate: () => {
        let output = "";
        if (reporter.subscriber.config.describeJson) {
          output +=
            self.migrationStatus({
              status: "preMigrate",
              data
            }) + "\n";
        }

        output += self.doubleline(`${data.file}`);
        return output;
      },

      saving: () => `\n   * Saving migration`,

      firstMigrate: () => {
        let output = ``;
        reporter.subscriber.config.dryRun
          ? (output +=
              self.doubleline(`Migrations dry-run (simulation)`) + "\n")
          : (output += self.doubleline(`Starting migrations...`) + "\n");

        output +=
          `> Network name:    '${data.network}'\n` +
          `> Network id:      ${data.networkId}\n` +
          `> Block gas limit: ${self.decAndHex(data.blockLimit)}\n`;
        return output;
      },

      postMigrate: () => {
        let output = "";
        let deployments =
          self.reporter.summary[self.reporter.currentFileIndex].deployments;

        if (!self.reporter.subscriber.config.dryRun && deployments.length) {
          output += `   > Saving artifacts\n`;
        }

        output +=
          self.underline(37) +
          "\n" +
          `   > ${"Total cost:".padEnd(15)} ${data.cost.padStart(
            15
          )} ${valueUnit}\n`;

        if (self.reporter.subscriber.config.describeJson) {
          output +=
            "\n" +
            self.migrationStatus({
              status: "postMigrate",
              data
            }) +
            "\n";
        }

        return output;
      },

      lastMigrate: () => {
        let output = "";

        output +=
          self.doubleline("Summary") +
          "\n" +
          `> ${"Total deployments:".padEnd(20)} ${data.totalDeployments}\n` +
          `> ${"Final cost:".padEnd(20)} ${data.finalCost} ${valueUnit}\n`;

        if (self.reporter.subscriber.config.describeJson) {
          output +=
            "\n" +
            self.migrationStatus({
              status: "lastMigrate",
              data: {
                totalDeployments: data.totalDeployments,
                finalCost: data.finalCost
              }
            }) +
            "\n";
        }

        return output;
      },

      // Batch
      many: () => self.underline(`Deploying Batch`),

      listMany: () => `   * ${data.contractName}`
    };

    return kinds[kind]();
  }
}

module.exports = Messages;