@solarity/hardhat-migrate
Version:
The simplest way to deploy smart contracts
424 lines • 20.8 kB
JavaScript
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
var _, done = false;
for (var i = decorators.length - 1; i >= 0; i--) {
var context = {};
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
if (kind === "accessor") {
if (result === void 0) continue;
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
if (_ = accept(result.get)) descriptor.get = _;
if (_ = accept(result.set)) descriptor.set = _;
if (_ = accept(result.init)) initializers.unshift(_);
}
else if (_ = accept(result)) {
if (kind === "field") initializers.unshift(_);
else descriptor[key] = _;
}
}
if (target) Object.defineProperty(target, contextIn.name, descriptor);
done = true;
};
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
var useValue = arguments.length > 2;
for (var i = 0; i < initializers.length; i++) {
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
}
return useValue ? value : void 0;
};
/* eslint-disable no-console */
import ora from "ora";
import { Network, id } from "ethers";
import { ReporterStorage } from "./ReporterStorage.js";
import { networkManager } from "../network/NetworkManager.js";
import { castAmount, CatchClassError, underline } from "../../utils/index.js";
import { predefinedChains } from "../../../types/verifier.js";
/**
* Global error handling for network-related issues is conducted within the NetworkManager class
*/
let BaseReporter = (() => {
let _classDecorators = [CatchClassError];
let _classDescriptor;
let _classExtraInitializers = [];
let _classThis;
var BaseReporter = class {
static { _classThis = this; }
static {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
BaseReporter = _classThis = _classDescriptor.value;
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
__runInitializers(_classThis, _classExtraInitializers);
}
_hre = {};
_config = {};
_network = {};
_spinner = null;
_spinnerMessage = null;
_spinnerInterval = null;
_spinnerState = [];
_nativeSymbol = "";
_explorerUrl = "";
_txExplorerUrl = "";
_warningsToPrint = new Map();
_storage = null;
async init(hre) {
this._hre = hre;
this._config = hre.config.migrate;
this._network = await this._getNetwork();
this._nativeSymbol = await this._getNativeSymbol();
this._explorerUrl = await this.getExplorerUrl();
try {
this._txExplorerUrl = this._explorerUrl !== "" ? new URL("tx/", this._explorerUrl).toString() : "";
}
catch {
this._txExplorerUrl = "";
}
this._storage = new ReporterStorage(hre);
}
async completeReport() {
await this._storage.finishReport();
}
notifyStorageAboutContracts(contracts) {
this._storage.storeReportedContracts(contracts);
}
reportMigrationBegin(files) {
this._reportMigrationFiles(files);
this._reportChainInfo();
console.log("\nStarting migration...\n");
this._storage.storeMigrationBegin();
}
reportMigrationFileBegin(file) {
console.log(`\n${underline(`Running ${file}...`)}`);
this._storage.storeMigrationFileBegin(file);
}
reportTransactionResponseHeader(tx, instanceName) {
console.log("\n" + underline(this._parseTransactionTitle(tx, instanceName)));
const txLink = this._getExplorerLink(tx.hash);
console.log(`> explorer: ${txLink}`);
this._storage.storeTransactionResponseHeader(tx, instanceName, txLink);
}
async startTxReporting(tx) {
if (this._hre.config.migrate.execution.withoutCLIReporting) {
return;
}
const timeStart = Date.now();
const blockStart = await networkManager.provider.provider.getBlockNumber();
const formatPendingTimeTask = async () => this._formatPendingTime(tx, timeStart, blockStart);
return this.startSpinner("tx-report", formatPendingTimeTask);
}
async startSpinner(id, getSpinnerText) {
if (this._spinnerState.includes(id))
return;
if (this._spinnerState.length === 0) {
this._spinner = ora(await getSpinnerText()).start();
this._spinnerInterval = setInterval(async () => {
if (!this._spinner) {
clearInterval(this._spinnerInterval);
return;
}
this._spinner.text = await getSpinnerText();
}, this._config.execution.transactionStatusCheckInterval);
}
this._spinnerState.push(id);
}
stopSpinner() {
if (!this._spinner)
return;
this._spinnerMessage = null;
this._spinnerState.pop();
if (this._spinnerState.length === 0) {
clearInterval(this._spinnerInterval);
this._spinner.stop();
this._spinner = null;
}
}
async reportTransactionReceipt(receipt) {
let output = "";
if (receipt.contractAddress) {
output += `> contractAddress: ${receipt.contractAddress}\n`;
}
const nativeSymbol = this._nativeSymbol;
output += `> blockNumber: ${receipt.blockNumber}\n`;
output += `> account: ${receipt.from}\n`;
const value = (await receipt.getTransaction()).value;
output += `> value: ${castAmount(value, nativeSymbol)}\n`;
output += `> balance: ${castAmount(await receipt.provider.getBalance(receipt.from), nativeSymbol)}\n`;
output += `> gasUsed: ${receipt.gasUsed}\n`;
output += `> gasPrice: ${castAmount(receipt.gasPrice, nativeSymbol)}\n`;
output += `> fee: ${castAmount(receipt.fee, nativeSymbol)}\n`;
console.log(output);
this._storage.storeTransactionReceipt(receipt, value);
}
summary(totalTransactions, totalCost) {
const output = `> ${"Total transactions:".padEnd(20)} ${totalTransactions}\n` +
`> ${"Final cost:".padEnd(20)} ${castAmount(totalCost, this._nativeSymbol)}\n`;
console.log(`\n${output}`);
this.reportWarnings();
this._storage.completeReport();
}
notifyDeploymentInsteadOfRecovery(contractName) {
const output = `\nCan't recover contract address for ${contractName}. Deploying instead...`;
console.log(output);
}
notifyDeploymentOfMissingLibrary(libraryName) {
const output = `\nDeploying missing library ${libraryName}...`;
console.log(output);
this._storage.storeDeploymentOfMissingLibrary(libraryName);
}
notifyTransactionSendingInsteadOfRecovery(contractMethod) {
const output = `\nCan't recover transaction for ${contractMethod}. Sending instead...`;
console.log(output);
}
notifyContractRecovery(contractName, contractAddress) {
const output = `\nContract address for ${contractName} has been recovered: ${contractAddress}`;
console.log(output);
this._storage.storeContractRecovery(contractName, contractAddress);
}
notifyTransactionRecovery(methodString, savedTx) {
const output = `\nTransaction ${methodString} has been recovered.`;
console.log(output);
this._storage.storeTransactionRecovery(methodString, savedTx);
}
reportVerificationBatchBegin() {
console.log("\nStarting verification of all deployed contracts");
}
reportNothingToVerify() {
console.log(`\nNothing to verify. Selected network is ${this._network.name}`);
this._storage.storeNothingToVerify();
}
reportSuccessfulVerification(contractAddress, contractName) {
const output = `\nContract ${contractName} (${contractAddress}) verified successfully.`;
console.log(output);
this._storage.storeVerificationSuccess(contractName, contractAddress);
}
reportAlreadyVerified(contractAddress, contractName) {
const output = `\nContract ${contractName} (${contractAddress}) already verified.`;
console.log(output);
this._storage.storeAlreadyVerified(contractName, contractAddress);
}
reportVerificationError(contractAddress, contractName, message) {
const output = `\nContract ${contractName} (${contractAddress}) verification failed: ${message}`;
console.log(output);
this._storage.storeVerificationFailure(contractName, contractAddress);
}
reportVerificationFailedToSave(contractName) {
const output = `\nFailed to save verification arguments for contract: ${contractName}`;
console.log(output);
this._storage.storeVerificationSaveFailure(contractName);
}
notifyContractCollisionByName(oldData, dataToSave) {
const output = `\nContract collision by Contract Name detected!`;
this._printContractCollision(output, oldData, dataToSave);
}
notifyContractCollisionByKeyFields(oldData, dataToSave) {
let output = `\nContract collision by key fields detected!`;
output += `\nKey fields are bytecode, from, chainId, value and contract name`;
this._printContractCollision(output, oldData, dataToSave);
}
notifyTransactionCollision(oldData, dataToSave) {
let output = `\nTransaction collision detected!`;
output += `\n> Previous Collision Details: `;
output += `\n\t- Migration Number: ${oldData.metadata.migrationNumber}`;
output += `\n\t- Method Name: ${oldData.metadata.methodName || "N/A"}`;
output += `\n> New Collision Details: `;
output += `\n\t- Migration Number: ${dataToSave.metadata.migrationNumber}`;
output += `\n\t- Method Name: ${dataToSave.metadata.methodName || "N/A"}`;
const key = id(output);
if (!this._warningsToPrint.has(key)) {
console.log(output);
}
this._warningsToPrint.set(key, output);
this._storage.storeTransactionCollision(oldData, dataToSave);
}
addWarning(warning) {
this._warningsToPrint.set(id(warning), warning);
}
notifyUnknownCollision(metadata, dataToSave) {
let output = `\nUnknown collision detected!`;
output += `\n> Previous Collision Details: `;
output += `\n\t- Migration Number: ${metadata.migrationNumber}`;
output += `\n\t- Method Name: ${metadata.methodName || "N/A"}`;
output += `\n\t- Contract Name: ${metadata.contractName || "N/A"}`;
output += `\n> New Collision Details: `;
output += `\n\t- Migration Number: ${dataToSave.metadata.migrationNumber}`;
output += `\n\t- Method Name: ${dataToSave.metadata.methodName || "N/A"}`;
output += `\n\t- Contract Name: ${dataToSave.metadata.contractName || "N/A"}`;
const key = id(output);
if (!this._warningsToPrint.has(key)) {
console.log(output);
}
this._warningsToPrint.set(key, output);
this._storage.storeUnknownCollision(metadata, dataToSave);
}
reportWarnings() {
if (this.getWarningsCount() === 0) {
return;
}
console.log("\nWarnings:");
this._warningsToPrint.forEach((warning) => {
console.log(warning);
});
console.log("\n\nDue to the detected collision(s), there's a high likelihood that migration recovery using '--continue' may not function as expected.\n" +
"To mitigate this, consider specifying a unique name for the contract during deployment.\n");
console.log("");
}
getWarningsCount() {
return this._warningsToPrint.size;
}
_printContractCollision(output, oldData, dataToSave) {
output += `\n> Contract: ${oldData.contractKeyData?.name || dataToSave.contractKeyData?.name}`;
output += `\n> Previous Collision Details: `;
output += `\n\t- Migration Number: ${oldData.metadata.migrationNumber}`;
output += `\n\t- Contract Address: ${oldData.contractAddress}`;
output += `\n> New Collision Details: `;
output += `\n\t- Migration Number: ${dataToSave.metadata.migrationNumber}`;
output += `\n\t- Contract Address: ${dataToSave.contractAddress}`;
const key = id(`${oldData.contractAddress}-${dataToSave.contractAddress}`);
if (!this._warningsToPrint.has(key)) {
console.log(output);
}
this._warningsToPrint.set(key, output);
this._storage.storeContractCollision(oldData, dataToSave);
}
async getExplorerUrl() {
const chainId = Number(this._network.chainId);
const customChain = this._getInfoFromHardhatConfig(chainId);
if (customChain) {
return customChain.urls.browserURL;
}
if (predefinedChains[chainId] &&
predefinedChains[chainId].explorers !== undefined &&
predefinedChains[chainId].explorers.length > 0) {
return predefinedChains[chainId].explorers[0].url;
}
const chain = await this._getChainMetadataById(chainId);
return chain.explorers[0].url;
}
reportSuccessfulProxyLinking(proxyAddress, implementationAddress) {
console.log(`Proxy ${proxyAddress} linked to implementation ${implementationAddress}`);
this._storage.storeSuccessfulProxyLinking(proxyAddress, implementationAddress);
}
reportFailedProxyLinking(proxyAddress, implementationAddress, result) {
console.log(`Failed to link proxy ${proxyAddress} to implementation ${implementationAddress}: ${result}`);
this._storage.storeFailedProxyLinking(proxyAddress, implementationAddress);
}
notifyOfProxyConstructorUsage(proxyFactoryName, constructorName) {
console.log(`\nProxy constructor ${constructorName} used in ${proxyFactoryName} (proxy)\n`);
}
_parseTransactionTitle(tx, instanceName) {
if (tx.to === null) {
if (instanceName.split(":").length == 1) {
return `Deploying ${instanceName}`;
}
return `Deploying${instanceName ? " " + instanceName.split(":")[1] : ""}`;
}
return `Transaction: ${instanceName}`;
}
async _formatPendingTime(tx, startTime, blockStart) {
if (this._spinnerMessage) {
return this._spinnerMessage;
}
return `Confirmations: ${await tx.confirmations()}; Blocks: ${(await networkManager.provider.provider.getBlockNumber()) - blockStart}; Seconds: ${((Date.now() - startTime) / 1000).toFixed(0)}`;
}
_getExplorerLink(txHash) {
try {
return this._txExplorerUrl !== "" ? new URL(txHash, this._txExplorerUrl).toString() : `tx/${txHash}`;
}
catch {
return `tx/${txHash}`;
}
}
_reportMigrationFiles(files) {
console.log("\nMigration files:");
files.forEach((file) => {
console.log(`> ${file}`);
});
console.log("");
this._storage.storeMigrationFiles(files);
}
_reportChainInfo() {
console.log(`> ${"Network:".padEnd(20)} ${this._network.name}`);
console.log(`> ${"Network id:".padEnd(20)} ${this._network.chainId}`);
this._storage.storeChainInfo({ network: this._network, explorer: this._explorerUrl });
}
async _getNetwork() {
try {
return networkManager.provider.provider.getNetwork();
}
catch {
return new Network("Local Ethereum", 1337);
}
}
async _getNativeSymbol() {
const chainId = Number(this._network.chainId);
if (predefinedChains[chainId]) {
return predefinedChains[chainId].nativeCurrency.symbol;
}
const chain = await this._getChainMetadataById(chainId);
return chain.nativeCurrency.symbol;
}
async _getChainMetadataById(chainId) {
let chain;
try {
const chains = await this._tryGetAllRecords();
chain = chains.find((chain) => chain.chainId === chainId) ?? predefinedChains[1337];
}
catch {
chain = predefinedChains[1337];
}
if (chain.explorers === undefined || chain.explorers.length === 0) {
chain.explorers = [
{
url: "",
name: "",
},
];
}
const hardhatChainInfo = this._getInfoFromHardhatConfig(chainId);
if (hardhatChainInfo && chain.explorers.length > 0 && hardhatChainInfo.urls.browserURL !== chain.explorers[0].url) {
chain.explorers[0].url = hardhatChainInfo.urls.browserURL;
// Also we reset the Native Currency symbol as it may be different
chain.nativeCurrency.symbol = predefinedChains[1337].nativeCurrency.symbol;
}
predefinedChains[chainId] = chain;
return chain;
}
_getInfoFromHardhatConfig(chainId) {
const cfg = this._hre.config;
const etherscanChains = cfg.etherscan?.customChains ?? [];
const blockscoutChains = cfg.blockscout?.customChains ?? [];
const merged = [...blockscoutChains, ...etherscanChains];
const deduped = merged.filter((chain, index, self) => index === self.findIndex((c) => c.chainId === chain.chainId));
return deduped.find((chain) => chain.chainId === chainId);
}
async _tryGetAllRecords() {
const url = "https://chainid.network/chains.json";
const response = await networkManager.axios.get(url);
// Assuming the JSON response is an array of record objects
return response.data;
}
};
return BaseReporter = _classThis;
})();
export let Reporter = null;
export async function createAndInitReporter(hre) {
if (Reporter) {
return;
}
Reporter = new BaseReporter();
await Reporter.init(hre);
}
/**
* Used only in test environments to ensure test atomicity
*/
export function resetReporter() {
Reporter = null;
}
//# sourceMappingURL=Reporter.js.map