diff --git a/CHANGELOG.md b/CHANGELOG.md index eaddc3ff..46f905dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Version 2.0.0-alpha.11 + +* Initially, the fully qualified name is used to retrieve a contract from the Transaction Storage. +* If the fully qualified name is absent, the recovery process falls back to using the `ContractFieldsToSave` derived from the contract. +* Should the name be located, the `ContractFieldsToSave` is disregarded and is not used as a key within the Transaction Storage. + ## Version 2.0.0-alpha.2 * Library linking fully relies on the hardhat artifacts. diff --git a/package.json b/package.json index 87ffd652..8471734d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@solarity/hardhat-migrate", - "version": "2.0.0-alpha.10", + "version": "2.0.0-alpha.11", "description": "Automatic deployment and verification of smart contracts", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", diff --git a/src/deployer/Deployer.ts b/src/deployer/Deployer.ts index 4fe24d6e..ce93cd18 100644 --- a/src/deployer/Deployer.ts +++ b/src/deployer/Deployer.ts @@ -7,16 +7,17 @@ import { catchError, getChainId, getSignerHelper, isDeployedContractAddress } fr import { MigrateError } from "../errors"; import { Adapter } from "./adapters/Adapter"; +import { TruffleAdapter } from "./adapters/TruffleAdapter"; import { BytecodeAdapter } from "./adapters/BytecodeAdapter"; import { EthersContractAdapter } from "./adapters/EthersContractAdapter"; -import { TruffleAdapter } from "./adapters/TruffleAdapter"; import { EthersFactoryAdapter } from "./adapters/EthersFactoryAdapter"; import { OverridesAndLibs } from "../types/deployer"; -import { KeyTransactionFields } from "../types/tools"; +import { KeyTransactionFields, MigrationMetadata } from "../types/tools"; import { Instance, TypedArgs } from "../types/adapter"; import { isContractFactory, isEthersContract, isBytecodeFactory, isTruffleFactory } from "../types/type-checks"; +import { Stats } from "../tools/Stats"; import { Reporter } from "../tools/reporters/Reporter"; import { TransactionProcessor } from "../tools/storage/TransactionProcessor"; @@ -58,8 +59,6 @@ export class Deployer { throw new MigrateError(`Contract with address '${contractIdentifier}' is not deployed`); } - TransactionProcessor.saveDeploymentTransactionWithContractName(defaultContractName, contractIdentifier); - return adapter.toInstance(contract, contractIdentifier, {}); } @@ -89,12 +88,17 @@ export class Deployer { const txResponse = await signer.sendTransaction(tx); - await Promise.all([ + const [receipt] = await Promise.all([ txResponse.wait(this._hre.config.migrate.wait), Reporter.reportTransaction(txResponse, methodString), ]); - TransactionProcessor.saveTransaction(tx); + const saveMetadata: MigrationMetadata = { + migrationNumber: Stats.currentMigration, + methodName: methodString, + }; + + TransactionProcessor.saveTransaction(tx, receipt!, saveMetadata); } public async getSigner(from?: string): Promise { diff --git a/src/deployer/MinimalContract.ts b/src/deployer/MinimalContract.ts index bd3714c6..c076057a 100644 --- a/src/deployer/MinimalContract.ts +++ b/src/deployer/MinimalContract.ts @@ -8,9 +8,11 @@ import { catchError, fillParameters, getChainId, getInterfaceOnlyWithConstructor import { MigrateError } from "../errors"; +import { MigrationMetadata } from "../types/tools"; import { MigrateConfig } from "../types/migrations"; import { ContractDeployTransactionWithContractName, OverridesAndLibs } from "../types/deployer"; +import { Stats } from "../tools/Stats"; import { Reporter } from "../tools/reporters/Reporter"; import { ArtifactProcessor } from "../tools/storage/ArtifactProcessor"; import { TransactionProcessor } from "../tools/storage/TransactionProcessor"; @@ -129,7 +131,12 @@ export class MinimalContract { await this._saveContractForVerification(contractAddress, tx, args); - TransactionProcessor.saveDeploymentTransaction(tx, tx.contractName, contractAddress); + const saveMetadata: MigrationMetadata = { + migrationNumber: Stats.currentMigration, + contractName: tx.contractName, + }; + + TransactionProcessor.saveDeploymentTransaction(tx, tx.contractName, contractAddress, saveMetadata); return contractAddress; } diff --git a/src/deployer/adapters/AbstractEthersAdapter.ts b/src/deployer/adapters/AbstractEthersAdapter.ts index 64abf448..e3c4ccb4 100644 --- a/src/deployer/adapters/AbstractEthersAdapter.ts +++ b/src/deployer/adapters/AbstractEthersAdapter.ts @@ -2,6 +2,7 @@ import { BaseContract, BaseContractMethod, ContractFactory, + ContractTransactionReceipt, ContractTransactionResponse, defineProperties, FunctionFragment, @@ -16,9 +17,10 @@ import "../../type-extensions"; import { bytecodeToString, fillParameters, getMethodString, getSignerHelper } from "../../utils"; import { OverridesAndLibs, OverridesAndName } from "../../types/deployer"; -import { KeyTransactionFields } from "../../types/tools"; import { EthersContract, BytecodeFactory } from "../../types/adapter"; +import { KeyTransactionFields, MigrationMetadata, TransactionFieldsToSave } from "../../types/tools"; +import { Stats } from "../../tools/Stats"; import { Reporter } from "../../tools/reporters/Reporter"; import { TransactionProcessor } from "../../tools/storage/TransactionProcessor"; @@ -141,11 +143,11 @@ export abstract class AbstractEthersAdapter extends Adapter { args: any[], ) { try { - const txResponse = TransactionProcessor.tryRestoreSavedTransaction(tx); + const savedTransaction = TransactionProcessor.tryRestoreSavedTransaction(tx); Reporter.notifyTransactionRecovery(methodString); - return txResponse; + return this._wrapTransactionFieldsToSave(savedTransaction); } catch { Reporter.notifyTransactionSendingInsteadOfRecovery(methodString); @@ -161,10 +163,24 @@ export abstract class AbstractEthersAdapter extends Adapter { ) { const txResponse: ContractTransactionResponse = (await oldMethod(...args)) as ContractTransactionResponse; - TransactionProcessor.saveTransaction(tx); + const saveMetadata: MigrationMetadata = { + migrationNumber: Stats.currentMigration, + methodName: methodString, + }; + + TransactionProcessor.saveTransaction(tx, (await txResponse.wait())!, saveMetadata); await Reporter.reportTransaction(txResponse, methodString); return txResponse; } + + private _wrapTransactionFieldsToSave(data: TransactionFieldsToSave): ContractTransactionResponse { + return { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + wait(_confirms?: number): Promise { + return data.receipt as unknown as Promise; + }, + } as unknown as ContractTransactionResponse; + } } diff --git a/src/deployer/adapters/EthersContractAdapter.ts b/src/deployer/adapters/EthersContractAdapter.ts index 1da738fe..2c75a6e6 100644 --- a/src/deployer/adapters/EthersContractAdapter.ts +++ b/src/deployer/adapters/EthersContractAdapter.ts @@ -6,6 +6,7 @@ import { catchError, getSignerHelper } from "../../utils"; import { EthersContract } from "../../types/adapter"; import { OverridesAndName } from "../../types/deployer"; +import { UNKNOWN_CONTRACT_NAME } from "../../types/tools"; import { ArtifactProcessor } from "../../tools/storage/ArtifactProcessor"; @@ -31,7 +32,7 @@ export class EthersContractAdapter extends AbstractEthersAdapter { return (instance as any).contractName; } - return "Unknown Contract"; + return UNKNOWN_CONTRACT_NAME; } } diff --git a/src/deployer/adapters/EthersFactoryAdapter.ts b/src/deployer/adapters/EthersFactoryAdapter.ts index cdd5b6fb..dbe029f2 100644 --- a/src/deployer/adapters/EthersFactoryAdapter.ts +++ b/src/deployer/adapters/EthersFactoryAdapter.ts @@ -4,6 +4,8 @@ import { AbstractEthersAdapter } from "./AbstractEthersAdapter"; import { catchError } from "../../utils"; +import { UNKNOWN_CONTRACT_NAME } from "../../types/tools"; + import { ArtifactProcessor } from "../../tools/storage/ArtifactProcessor"; @catchError @@ -20,7 +22,7 @@ export class EthersFactoryAdapter extends AbstractEthersAdapter { try { return ArtifactProcessor.tryGetContractName(this.getRawBytecode(instance)); } catch { - return "Unknown Contract"; + return UNKNOWN_CONTRACT_NAME; } } diff --git a/src/deployer/adapters/TruffleAdapter.ts b/src/deployer/adapters/TruffleAdapter.ts index 63a5d098..1c7b19c8 100644 --- a/src/deployer/adapters/TruffleAdapter.ts +++ b/src/deployer/adapters/TruffleAdapter.ts @@ -1,4 +1,4 @@ -import { Interface, toBigInt } from "ethers"; +import { Interface, toBigInt, TransactionReceiptParams } from "ethers"; import { HardhatRuntimeEnvironment } from "hardhat/types"; @@ -8,7 +8,7 @@ import { Adapter } from "./Adapter"; import { MinimalContract } from "../MinimalContract"; -import { bytecodeToString, catchError, fillParameters, getMethodString, toJSON } from "../../utils"; +import { bytecodeToString, catchError, fillParameters, getMethodString } from "../../utils"; import { EthersContract, Instance, TruffleFactory } from "../../types/adapter"; import { @@ -17,8 +17,9 @@ import { OverridesAndName, TruffleTransactionResponse, } from "../../types/deployer"; -import { KeyTransactionFields } from "../../types/tools"; +import { KeyTransactionFields, MigrationMetadata, UNKNOWN_CONTRACT_NAME } from "../../types/tools"; +import { Stats } from "../../tools/Stats"; import { Reporter } from "../../tools/reporters/Reporter"; import { TruffleReporter } from "../../tools/reporters/TruffleReporter"; import { ArtifactProcessor } from "../../tools/storage/ArtifactProcessor"; @@ -78,7 +79,7 @@ export class TruffleAdapter extends Adapter { return (instance as any).contractName; } - return "Unknown Contract"; + return UNKNOWN_CONTRACT_NAME; } } @@ -115,13 +116,21 @@ export class TruffleAdapter extends Adapter { continue; } - (contract as any)[methodName] = this._wrapOldMethod(contractName, methodName, oldMethod, to, parameters); + (contract as any)[methodName] = this._wrapOldMethod( + contractInterface, + contractName, + methodName, + oldMethod, + to, + parameters, + ); } return contract; } protected _wrapOldMethod( + contractInterface: Interface, contractName: string, methodName: string, oldMethod: BaseTruffleMethod, @@ -129,19 +138,25 @@ export class TruffleAdapter extends Adapter { parameters: OverridesAndLibs, ): (...args: any[]) => Promise { return async (...args: any[]): Promise => { - const onlyToSaveTx = await this._buildContractDeployTransaction(args, to, parameters); - const methodString = getMethodString(contractName, methodName); - TruffleReporter.notifyTransactionSending(methodString); + const txData = contractInterface.encodeFunctionData(methodName, args); + const onlyToSaveTx = await this._buildContractDeployTransaction(txData, to, parameters); if (this._config.continue) { return this._recoverTransaction(methodString, onlyToSaveTx, oldMethod, args); } + TruffleReporter.notifyTransactionSending(methodString); + const txResult = await oldMethod(...args); - TransactionProcessor.saveTransaction(onlyToSaveTx); + const saveMetadata: MigrationMetadata = { + migrationNumber: Stats.currentMigration, + methodName: methodString, + }; + + TransactionProcessor.saveTransaction(onlyToSaveTx, this._toTransactionReceipt(txResult), saveMetadata); await TruffleReporter.reportTransaction(txResult, methodString); @@ -176,7 +191,12 @@ export class TruffleAdapter extends Adapter { ) { const txResult = await oldMethod(...args); - TransactionProcessor.saveTransaction(tx); + const saveMetadata: MigrationMetadata = { + migrationNumber: Stats.currentMigration, + methodName: methodString, + }; + + TransactionProcessor.saveTransaction(tx, this._toTransactionReceipt(txResult), saveMetadata); await TruffleReporter.reportTransaction(txResult, methodString); @@ -187,7 +207,7 @@ export class TruffleAdapter extends Adapter { * @dev Build a transaction ONLY to save it in the storage. */ private async _buildContractDeployTransaction( - args: any[], + data: string, to: string, parameters: OverridesAndLibs, ): Promise { @@ -196,9 +216,37 @@ export class TruffleAdapter extends Adapter { return { to: to, from: parameters.from! as string, - data: toJSON(args), + data: data, chainId: toBigInt(String(parameters.chainId)), value: toBigInt(String(parameters.value)), }; } + + private _toTransactionReceipt(tx: TruffleTransactionResponse): TransactionReceiptParams { + let txGasPrice: bigint = 0n; + + if (tx.receipt.effectiveGasPrice != null) { + txGasPrice = toBigInt(tx.receipt.effectiveGasPrice); + } else if (tx.receipt.gasPrice != null) { + txGasPrice = toBigInt(tx.receipt.gasPrice); + } + + return { + to: tx.receipt.to, + from: tx.receipt.from, + contractAddress: tx.receipt.contractAddress !== undefined ? tx.receipt.contractAddress : null, + hash: tx.receipt.transactionHash, + index: Number(tx.receipt.transactionIndex), + blockHash: tx.receipt.blockHash, + blockNumber: Number(tx.receipt.blockNumber), + logsBloom: tx.receipt.logsBloom ? tx.receipt.logsBloom : "", + logs: tx.logs !== undefined ? tx.logs : [], + gasUsed: tx.receipt.gasUsed ? toBigInt(tx.receipt.gasUsed) : 0n, + cumulativeGasUsed: tx.receipt.cumulativeGasUsed ? toBigInt(tx.receipt.cumulativeGasUsed) : 0n, + gasPrice: txGasPrice, + type: tx.receipt.type ? Number(tx.receipt.type) : 0, + status: tx.receipt.status ? Number(tx.receipt.status) : null, + root: null, + }; + } } diff --git a/src/migrator/Migrator.ts b/src/migrator/Migrator.ts index 08523f1a..38b937af 100644 --- a/src/migrator/Migrator.ts +++ b/src/migrator/Migrator.ts @@ -15,6 +15,7 @@ import { MigrateConfig } from "../types/migrations"; import { Deployer } from "../deployer/Deployer"; import { Verifier } from "../verifier/Verifier"; +import { Stats } from "../tools/Stats"; import { Reporter } from "../tools/reporters/Reporter"; export class Migrator { @@ -40,7 +41,10 @@ export class Migrator { Reporter.reportMigrationBegin(this._migrationFiles); for (const element of this._migrationFiles) { + Stats.currentMigration = this._getMigrationNumber(element); + Reporter.reportMigrationFileBegin(element); + try { // eslint-disable-next-line @typescript-eslint/no-var-requires const migration = require(resolvePathToFile(this._config.pathToMigrations, element)); @@ -64,7 +68,7 @@ export class Migrator { const files = directoryContents .filter((file) => { - const migrationNumber = parseInt(basename(file)); + const migrationNumber = this._getMigrationNumber(file); if ( isNaN(migrationNumber) || @@ -80,7 +84,7 @@ export class Migrator { return statSync(resolvePathToFile(this._config.pathToMigrations, file)).isFile(); }) .sort((a, b) => { - return parseInt(basename(a)) - parseInt(basename(b)); + return this._getMigrationNumber(a) - this._getMigrationNumber(b); }); if (files.length === 0) { @@ -89,4 +93,8 @@ export class Migrator { return files; } + + private _getMigrationNumber(file: string) { + return parseInt(basename(file)); + } } diff --git a/src/tools/Stats.ts b/src/tools/Stats.ts new file mode 100644 index 00000000..800a6ced --- /dev/null +++ b/src/tools/Stats.ts @@ -0,0 +1,15 @@ +import { MigrationStats } from "../types/tools"; + +export class Stats { + private static _stats: MigrationStats = { + currentMigration: 0, + }; + + public static get currentMigration(): number { + return this._stats.currentMigration; + } + + public static set currentMigration(currentMigration: number) { + this._stats.currentMigration = currentMigration; + } +} diff --git a/src/tools/reporters/Reporter.ts b/src/tools/reporters/Reporter.ts index cb199839..fbaf9ad4 100644 --- a/src/tools/reporters/Reporter.ts +++ b/src/tools/reporters/Reporter.ts @@ -12,6 +12,7 @@ import { catchError, underline } from "../../utils"; import { MigrateConfig } from "../../types/migrations"; import { ChainRecord, predefinedChains } from "../../types/verifier"; +import { ContractFieldsToSave, MigrationMetadata, TransactionFieldsToSave } from "../../types/tools"; @catchError export class Reporter { @@ -23,6 +24,8 @@ export class Reporter { private static totalCost: bigint = 0n; private static totalTransactions: number = 0; + private static _warningsToPrint: string[] = []; + public static async init(config: MigrateConfig) { this._config = config; @@ -50,6 +53,8 @@ export class Reporter { `> ${"Final cost:".padEnd(20)} ${this.castAmount(this.totalCost, this._nativeSymbol)}\n`; console.log(`\n${output}`); + + this.reportWarnings(); } public static async reportTransactionByHash(txHash: string, instanceName: string) { @@ -68,7 +73,7 @@ export class Reporter { console.log("\n" + underline(this._parseTransactionTitle(tx, instanceName))); - console.log(`> explorer: ${await this._getExplorerLink(tx.hash)}`); + console.log(`> explorer: ${this._getExplorerLink(tx.hash)}`); const formatPendingTimeTask = async () => this._formatPendingTime(tx, timeStart, blockStart); @@ -114,47 +119,95 @@ export class Reporter { } public static notifyContractRecovery(contractName: string, contractAddress: string): void { - const output = `\nContract address for ${contractName} has been recovered: ${contractAddress}\n`; + const output = `\nContract address for ${contractName} has been recovered: ${contractAddress}`; console.log(output); } public static notifyTransactionRecovery(methodString: string): void { - const output = `\nTransaction ${methodString} has been recovered.\n`; + const output = `\nTransaction ${methodString} has been recovered.`; console.log(output); } public static reportVerificationBatchBegin() { - console.log("\nStarting verification of all deployed contracts\n"); + console.log("\nStarting verification of all deployed contracts"); } public static reportNothingToVerify() { - console.log(`\nNothing to verify. Selected network is ${this._network.name}\n`); + console.log(`\nNothing to verify. Selected network is ${this._network.name}`); } public static reportSuccessfulVerification(contractAddress: string, contractName: string) { - const output = `\nContract ${contractName} (${contractAddress}) verified successfully.\n`; + const output = `\nContract ${contractName} (${contractAddress}) verified successfully.`; console.log(output); } public static reportAlreadyVerified(contractAddress: string, contractName: string) { - const output = `\nContract ${contractName} (${contractAddress}) already verified.\n`; + const output = `\nContract ${contractName} (${contractAddress}) already verified.`; console.log(output); } public static reportVerificationError(contractAddress: string, contractName: string, message: string) { - const output = `\nContract ${contractName} (${contractAddress}) verification failed: ${message}\n`; + const output = `\nContract ${contractName} (${contractAddress}) verification failed: ${message}`; console.log(output); } public static reportVerificationFailedToSave(contractName: string) { - const output = `\nFailed to save verification arguments for contract: ${contractName}\n`; + const output = `\nFailed to save verification arguments for contract: ${contractName}`; + + console.log(output); + } + + public static notifyContractCollision(oldData: ContractFieldsToSave, dataToSave: ContractFieldsToSave) { + let output = `\nContract collision detected!`; + output += `\n> Contract: ${oldData.contractName || dataToSave.contractName}`; + 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}`; + + console.log(output); + + this._warningsToPrint.push(output); + } + + public static notifyTransactionCollision(oldData: TransactionFieldsToSave, dataToSave: TransactionFieldsToSave) { + 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"}`; console.log(output); + + this._warningsToPrint.push(output); + } + + public static notifyUnknownCollision( + metadata: MigrationMetadata, + dataToSave: TransactionFieldsToSave | ContractFieldsToSave, + ) { + 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"}`; + + console.log(output); + + this._warningsToPrint.push(output); } public static reportContracts(...contracts: [string, string][]): void { @@ -162,10 +215,30 @@ export class Reporter { Contract: contract, Address: address, })); + console.log(); console.table(table); console.log(); } + public static reportWarnings() { + if (this._warningsToPrint.length === 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(""); + } + private static _parseTransactionTitle(tx: TransactionResponse, instanceName: string): string { if (tx.to === null) { if (instanceName.split(":").length == 1) { diff --git a/src/tools/storage/TransactionProcessor.ts b/src/tools/storage/TransactionProcessor.ts index 22215424..92bf8fda 100644 --- a/src/tools/storage/TransactionProcessor.ts +++ b/src/tools/storage/TransactionProcessor.ts @@ -1,7 +1,9 @@ -import { ContractDeployTransaction, ContractTransactionResponse, isAddress } from "ethers"; +import { ContractDeployTransaction, isAddress, TransactionReceiptParams } from "ethers"; import { TransactionStorage } from "./MigrateStorage"; +import { Reporter } from "../reporters/Reporter"; + import { MigrateError } from "../../errors"; import { @@ -11,28 +13,61 @@ import { isDeployedContractAddress, } from "../../utils"; -import { KeyTransactionFields } from "../../types/tools"; +import { + ContractFieldsToSave, + KeyTransactionFields, + MigrationMetadata, + TransactionFieldsToSave, + UNKNOWN_CONTRACT_NAME, +} from "../../types/tools"; import { validateKeyDeploymentFields, validateKeyTxFields } from "../../types/type-checks"; @catchError export class TransactionProcessor { - private static _deployedContracts: Map = new Map(); - @catchError @validateKeyDeploymentFields - public static saveDeploymentTransaction(args: ContractDeployTransaction, contractName: string, address: string) { - this._saveContract(args, address); - this._saveContractByName(contractName, address); - } + public static saveDeploymentTransaction( + args: ContractDeployTransaction, + contractName: string, + contractAddress: string, + metadata: MigrationMetadata, + ) { + const dataToSave: ContractFieldsToSave = { + contractKeyData: { + data: args.data, + from: args.from!, + chainId: args.chainId!, + value: args.value!, + }, + contractAddress, + contractName, + metadata, + }; + + if (contractName === UNKNOWN_CONTRACT_NAME) { + this._saveContract(args, dataToSave); - public static saveDeploymentTransactionWithContractName(contractName: string, address: string) { - this._saveContractByName(contractName, address); + return; + } + + if (TransactionStorage.has(contractName)) { + this._processCollision(contractName, dataToSave); + } + + this._saveContractByName(contractName, dataToSave); } /** * @param tx - Transaction to save. Acts as a key and value at the same time. + * @param receipt + * @param metadata */ - public static saveTransaction(tx: KeyTransactionFields) { + @validateKeyTxFields + public static saveTransaction( + tx: KeyTransactionFields, + receipt: TransactionReceiptParams, + metadata: MigrationMetadata, + ) { this._saveTransaction( { data: tx.data, @@ -41,14 +76,15 @@ export class TransactionProcessor { to: tx.to, value: tx.value, }, - tx, + receipt, + metadata, ); } @catchError @validateKeyDeploymentFields public static async tryRestoreContractAddressByKeyFields(key: ContractDeployTransaction): Promise { - const contractAddress = this._tryGetDataFromStorage( + const restoredData = this._tryGetDataFromStorage( createKeyDeploymentFieldsHash({ data: key.data, from: key.from!, @@ -57,31 +93,27 @@ export class TransactionProcessor { }), ); - if (!isAddress(contractAddress) || !(await isDeployedContractAddress(contractAddress))) { + if (!isAddress(restoredData.contractAddress) || !(await isDeployedContractAddress(restoredData.contractAddress))) { throw new MigrateError(`Contract address is not valid`); } - return contractAddress; + return restoredData.contractAddress; } @catchError public static async tryRestoreContractAddressByName(contractName: string): Promise { - const contractAddress = this._tryGetDataFromStorage(contractName); + const restoredData: ContractFieldsToSave = this._tryGetDataFromStorage(contractName); - if (!isAddress(contractAddress) || !(await isDeployedContractAddress(contractAddress))) { + if (!isAddress(restoredData.contractAddress) || !(await isDeployedContractAddress(restoredData.contractAddress))) { throw new MigrateError(`Contract address is not valid`); } - return contractAddress; + return restoredData.contractAddress; } @catchError @validateKeyTxFields - public static tryRestoreSavedTransaction(key: KeyTransactionFields): ContractTransactionResponse { - if (this._deployedContracts.has(key.to)) { - throw new MigrateError(`Contract is deployed in the current migration`); - } - + public static tryRestoreSavedTransaction(key: KeyTransactionFields): TransactionFieldsToSave { return this._tryGetDataFromStorage( createKeyTxFieldsHash({ data: key.data, @@ -94,40 +126,67 @@ export class TransactionProcessor { } @catchError - @validateKeyTxFields - private static _saveTransaction(args: KeyTransactionFields, transaction: KeyTransactionFields) { - TransactionStorage.set( - createKeyTxFieldsHash({ - data: args.data, - from: args.from, - chainId: args.chainId, - to: args.to, - value: args.value, - }), - transaction, - true, - ); + private static _saveTransaction( + args: KeyTransactionFields, + transaction: TransactionReceiptParams, + metadata: MigrationMetadata, + ) { + const dataToSave: TransactionFieldsToSave = { + txKeyData: args, + receipt: transaction, + metadata, + }; + + const dataKey = createKeyTxFieldsHash({ + data: args.data, + from: args.from, + chainId: args.chainId, + to: args.to, + value: args.value, + }); + + if (TransactionStorage.has(dataKey)) { + this._processCollision(dataKey, dataToSave); + } + + TransactionStorage.set(dataKey, dataToSave, true); } - @catchError - @validateKeyDeploymentFields - private static _saveContract(args: ContractDeployTransaction, contractAddress: string) { - this._deployedContracts.set(contractAddress, true); + private static _saveContract(args: ContractDeployTransaction, dataToSave: ContractFieldsToSave) { + const keyByArgs = createKeyDeploymentFieldsHash({ + data: args.data, + from: args.from!, + chainId: args.chainId!, + value: args.value!, + }); - TransactionStorage.set( - createKeyDeploymentFieldsHash({ - data: args.data, - from: args.from!, - chainId: args.chainId!, - value: args.value!, - }), - contractAddress, - true, - ); + TransactionStorage.set(keyByArgs, dataToSave, true); + } + + private static _saveContractByName(contractName: string, dataToSave: ContractFieldsToSave) { + TransactionStorage.set(contractName, dataToSave, true); } - private static _saveContractByName(contractName: string, address: string) { - TransactionStorage.set(contractName, address, true); + private static _processCollision(dataKey: string, dataToSave: TransactionFieldsToSave | ContractFieldsToSave) { + const oldData: { + receipt?: TransactionReceiptParams; + contractAddress?: string; + metadata: MigrationMetadata; + } = TransactionStorage.get(dataKey); + + if (oldData.contractAddress) { + Reporter.notifyContractCollision(oldData as ContractFieldsToSave, dataToSave as ContractFieldsToSave); + + return; + } + + if (oldData.receipt) { + Reporter.notifyTransactionCollision(oldData as TransactionFieldsToSave, dataToSave as TransactionFieldsToSave); + + return; + } + + Reporter.notifyUnknownCollision(oldData.metadata, dataToSave); } private static _tryGetDataFromStorage(key: string): any { diff --git a/src/types/deployer.ts b/src/types/deployer.ts index f14f1eb0..c6c68bb6 100644 --- a/src/types/deployer.ts +++ b/src/types/deployer.ts @@ -30,6 +30,7 @@ export interface ArtifactExtended extends Artifact { export interface TruffleTransactionResponse { tx: string; receipt: TransactionReceipt; + logs?: any[]; } export interface TransactionReceipt { @@ -44,6 +45,8 @@ export interface TransactionReceipt { logsBloom?: string; type?: string; status?: string; + gasPrice?: string; + effectiveGasPrice?: string; cumulativeGasUsed?: string; contractAddress?: string; } diff --git a/src/types/tools.ts b/src/types/tools.ts index 0ef928ef..cee6a596 100644 --- a/src/types/tools.ts +++ b/src/types/tools.ts @@ -1,3 +1,5 @@ +import { TransactionReceiptParams } from "ethers"; + export interface KeyDeploymentFields { data: string; from: string; @@ -13,9 +15,34 @@ export interface KeyTransactionFields { value: bigint; } +export interface TransactionFieldsToSave { + txKeyData: KeyTransactionFields; + receipt: TransactionReceiptParams; + metadata: MigrationMetadata; +} + +export interface ContractFieldsToSave { + contractKeyData?: KeyDeploymentFields; + contractName?: string; + contractAddress: string; + metadata: MigrationMetadata; +} + +export interface MigrationMetadata { + migrationNumber: number; + contractName?: string; + methodName?: string; +} + +export interface MigrationStats { + currentMigration: number; +} + export enum StorageNamespaces { Storage = "storage", Artifacts = "artifacts", Transactions = "transactions", Verification = "verification", } + +export const UNKNOWN_CONTRACT_NAME = "Unknown Contract"; diff --git a/test/integration/adapters/ethersAdapter.ts b/test/integration/adapters/ethersAdapter.ts index ffc91738..fe7dc9ce 100644 --- a/test/integration/adapters/ethersAdapter.ts +++ b/test/integration/adapters/ethersAdapter.ts @@ -10,6 +10,7 @@ import { EthersFactoryAdapter } from "../../../src/deployer/adapters/EthersFacto import { ContractWithConstructorArguments__factory } from "../../fixture-projects/hardhat-project-minimal-typechain-ethers/typechain-types"; import { Provider } from "../../../src/tools/Provider"; import { Reporter } from "../../../src/tools/reporters/Reporter"; +import { Migrator } from "../../../src/migrator/Migrator"; describe("EthersAdapter", () => { describe("getContractDeployParams()", () => { @@ -51,7 +52,7 @@ describe("EthersAdapter", () => { beforeEach(async function () { await Provider.init(this.hre); - Reporter.init(this.hre.config.migrate); + await Reporter.init(this.hre.config.migrate); }); let ContractWithConstructor: ContractFactory; @@ -81,7 +82,7 @@ describe("EthersAdapter", () => { beforeEach(async function () { await Provider.init(this.hre); - Reporter.init(this.hre.config.migrate); + await Reporter.init(this.hre.config.migrate); }); beforeEach("setup", async function () { diff --git a/test/integration/adapters/truffleAdapter.ts b/test/integration/adapters/truffleAdapter.ts index d5d71e83..3b117c63 100644 --- a/test/integration/adapters/truffleAdapter.ts +++ b/test/integration/adapters/truffleAdapter.ts @@ -9,6 +9,7 @@ import { TruffleAdapter } from "../../../src/deployer/adapters/TruffleAdapter"; import { ArtifactProcessor } from "../../../src/tools/storage/ArtifactProcessor"; import { Provider } from "../../../src/tools/Provider"; import { Reporter } from "../../../src/tools/reporters/Reporter"; +import { Migrator } from "../../../src/migrator/Migrator"; describe("TruffleAdapter", () => { describe("getContractDeployParams()", () => { @@ -49,7 +50,7 @@ describe("TruffleAdapter", () => { beforeEach(async function () { await Provider.init(this.hre); - Reporter.init(this.hre.config.migrate); + await Reporter.init(this.hre.config.migrate); }); let contractWithConstructorArtifact: TruffleContract; @@ -79,7 +80,7 @@ describe("TruffleAdapter", () => { beforeEach(async function () { await Provider.init(this.hre); - Reporter.init(this.hre.config.migrate); + await Reporter.init(this.hre.config.migrate); }); let contractWithConstructorArtifact: TruffleContract; diff --git a/test/integration/deployer/deployEthers.ts b/test/integration/deployer/deployEthers.ts index 4420bc5e..c22ff8d1 100644 --- a/test/integration/deployer/deployEthers.ts +++ b/test/integration/deployer/deployEthers.ts @@ -11,6 +11,7 @@ import { ContractWithConstructorArguments__factory, ContractWithPayableConstructor__factory, } from "../../fixture-projects/hardhat-project-minimal-typechain-ethers/typechain-types"; +import { Migrator } from "../../../src/migrator/Migrator"; describe("deployer", () => { describe("deploy()", () => { @@ -18,7 +19,7 @@ describe("deployer", () => { beforeEach(async function () { await Provider.init(this.hre); - Reporter.init(this.hre.config.migrate); + await Reporter.init(this.hre.config.migrate); }); let deployer: Deployer; diff --git a/test/integration/deployer/deployTruffle.ts b/test/integration/deployer/deployTruffle.ts index af545d74..fdbcb4ca 100644 --- a/test/integration/deployer/deployTruffle.ts +++ b/test/integration/deployer/deployTruffle.ts @@ -9,6 +9,7 @@ import { Deployer } from "../../../src/deployer/Deployer"; import { TransactionStorage } from "../../../src/tools/storage/MigrateStorage"; import { Reporter } from "../../../src/tools/reporters/Reporter"; import { Provider } from "../../../src/tools/Provider"; +import { Migrator } from "../../../src/migrator/Migrator"; describe("Truffle -- deployer", () => { describe("deploy()", () => { @@ -16,7 +17,7 @@ describe("Truffle -- deployer", () => { beforeEach(async function () { await Provider.init(this.hre); - Reporter.init(this.hre.config.migrate); + await Reporter.init(this.hre.config.migrate); }); let contractWithConstructorArtifact: TruffleContract; diff --git a/test/integration/deployer/deployTypechainEthers.ts b/test/integration/deployer/deployTypechainEthers.ts index 713d7212..4ba821a0 100644 --- a/test/integration/deployer/deployTypechainEthers.ts +++ b/test/integration/deployer/deployTypechainEthers.ts @@ -17,6 +17,7 @@ import { import { Reporter } from "../../../src/tools/reporters/Reporter"; import { Provider } from "../../../src/tools/Provider"; import { Linker } from "../../../src/deployer/Linker"; +import { Migrator } from "../../../src/migrator/Migrator"; describe("Ehters Typechain -- Deployer", () => { describe("deploy()", () => { @@ -24,7 +25,7 @@ describe("Ehters Typechain -- Deployer", () => { beforeEach(async function () { await Provider.init(this.hre); - Reporter.init(this.hre.config.migrate); + await Reporter.init(this.hre.config.migrate); Linker.setConfig(this.hre.config.migrate); }); diff --git a/test/integration/deployer/deployTypechainTruffle.ts b/test/integration/deployer/deployTypechainTruffle.ts index bc056222..2f452427 100644 --- a/test/integration/deployer/deployTypechainTruffle.ts +++ b/test/integration/deployer/deployTypechainTruffle.ts @@ -11,6 +11,7 @@ import { Reporter } from "../../../src/tools/reporters/Reporter"; import { TransactionStorage } from "../../../src/tools/storage/MigrateStorage"; import { Provider } from "../../../src/tools/Provider"; import { Linker } from "../../../src/deployer/Linker"; +import { Migrator } from "../../../src/migrator/Migrator"; describe("Truffle Typechain -- Deployer", () => { describe("deploy()", () => { @@ -18,7 +19,7 @@ describe("Truffle Typechain -- Deployer", () => { beforeEach(async function () { await Provider.init(this.hre); - Reporter.init(this.hre.config.migrate); + await Reporter.init(this.hre.config.migrate); Linker.setConfig(this.hre.config.migrate); }); diff --git a/test/integration/transaction-storage.ts b/test/integration/transaction-storage.ts index 92cb10ad..d2209b04 100644 --- a/test/integration/transaction-storage.ts +++ b/test/integration/transaction-storage.ts @@ -14,13 +14,14 @@ import { } from "../fixture-projects/hardhat-project-repeats-typechain-ethers/typechain-types"; import { Reporter } from "../../src/tools/reporters/Reporter"; import { Provider } from "../../src/tools/Provider"; +import { UNKNOWN_CONTRACT_NAME } from "../../src/types/tools"; describe("TransactionStorage", async () => { useEnvironment("repeats-typechain-ethers"); beforeEach(async function () { await Provider.init(this.hre); - Reporter.init(this.hre.config.migrate); + await Reporter.init(this.hre.config.migrate); await ArtifactProcessor.parseArtifacts(this.hre); }); @@ -37,7 +38,9 @@ describe("TransactionStorage", async () => { }); it("should save deployment transaction", async function () { - const contract = await deployer.deploy(ContractWithConstructorArguments__factory, ["hello"]); + const contract = await deployer.deploy(ContractWithConstructorArguments__factory, ["hello"], { + name: UNKNOWN_CONTRACT_NAME, + }); const factory = new ContractFactory( ContractWithConstructorArguments__factory.abi, @@ -67,7 +70,10 @@ describe("TransactionStorage", async () => { it("should save deployment transaction with transmitted ether", async function () { const value = BigInt(1); - const contract = await deployer.deploy(ContractWithPayableConstructor__factory, [], { value: value }); + const contract = await deployer.deploy(ContractWithPayableConstructor__factory, [], { + value: value, + name: UNKNOWN_CONTRACT_NAME, + }); const factory = new ContractFactory( ContractWithPayableConstructor__factory.abi, ContractWithPayableConstructor__factory.bytecode, @@ -121,7 +127,9 @@ describe("TransactionStorage", async () => { }); it("should not differ contracts with nonce", async function () { - const contract = await deployer.deploy(ContractWithConstructorArguments__factory, ["hello"]); + const contract = await deployer.deploy(ContractWithConstructorArguments__factory, ["hello"], { + name: UNKNOWN_CONTRACT_NAME, + }); const factory = new ContractFactory( ContractWithConstructorArguments__factory.abi,