@solarity/hardhat-migrate
Version:
The simplest way to deploy smart contracts
234 lines (189 loc) • 6.65 kB
text/typescript
import { ContractDeployTransaction, isAddress, TransactionReceiptParams } from "ethers";
import { isFullyQualifiedName } from "hardhat/utils/contract-names";
import { TransactionStorage } from "./MigrateStorage";
import { Reporter } from "../reporters/Reporter";
import { MigrateError } from "../../errors";
import {
catchError,
createKeyDeploymentFieldsHash,
createKeyTxFieldsHash,
isDeployedContractAddress,
} from "../../utils";
import {
ContractFieldsToSave,
KeyTransactionFields,
MigrationMetadata,
TransactionFieldsToSave,
} from "../../types/tools";
import { MigrateConfig } from "../../types/migrations";
import { ContractDeployTxWithName, TransactionReceipt } from "../../types/deployer";
import { validateKeyDeploymentFields, validateKeyTxFields } from "../../types/type-checks";
export class BaseTransactionProcessor {
constructor(protected _config: MigrateConfig) {}
public saveDeploymentTransaction(
args: ContractDeployTransaction,
contractName: string,
contractAddress: string,
metadata: MigrationMetadata,
) {
const dataToSave: ContractFieldsToSave = {
contractKeyData: {
name: contractName,
data: args.data,
from: args.from!,
chainId: args.chainId!,
value: args.value!,
},
contractAddress,
metadata,
};
const keyByArgs = createKeyDeploymentFieldsHash(dataToSave.contractKeyData!);
this._saveContract(keyByArgs, 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 saveTransaction(
tx: KeyTransactionFields,
receipt: TransactionReceiptParams | TransactionReceipt,
metadata: MigrationMetadata,
) {
return this._saveTransaction(
{
data: tx.data,
from: tx.from,
chainId: tx.chainId,
to: tx.to,
value: tx.value,
name: tx.name,
},
receipt,
metadata,
);
}
public saveContractAddress(contractName: string, contractAddress: string, metadata: MigrationMetadata) {
const dataToSave: ContractFieldsToSave = {
contractAddress,
metadata,
};
this._saveContractByName(contractName, dataToSave);
}
public async tryRestoreContractAddressByKeyFields(key: ContractDeployTxWithName): Promise<string> {
const restoredData = this._tryGetDataFromStorage(
createKeyDeploymentFieldsHash({
name: key.contractName,
data: key.data,
from: key.from!,
chainId: key.chainId!,
value: key.value!,
}),
);
if (!isAddress(restoredData.contractAddress) || !(await isDeployedContractAddress(restoredData.contractAddress))) {
throw new MigrateError(`Contract address is not valid`);
}
return restoredData.contractAddress;
}
public async tryRestoreContractAddressByName(contractName: string): Promise<string> {
const restoredData: ContractFieldsToSave = this._tryGetDataFromStorage(contractName);
if (!isAddress(restoredData.contractAddress) || !(await isDeployedContractAddress(restoredData.contractAddress))) {
throw new MigrateError(`Contract address is not valid`);
}
return restoredData.contractAddress;
}
public tryRestoreSavedTransaction(key: KeyTransactionFields): TransactionFieldsToSave {
return this._tryGetDataFromStorage(
createKeyTxFieldsHash({
data: key.data,
from: key.from,
chainId: key.chainId,
to: key.to,
value: key.value,
name: key.name,
}),
);
}
public tryRestoreSavedDeployedTxByContractName(contractName: string): ContractFieldsToSave {
return this._tryGetDataFromStorage(contractName);
}
private _saveTransaction(
args: KeyTransactionFields,
transaction: TransactionReceiptParams | TransactionReceipt,
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,
name: args.name,
});
if (TransactionStorage.has(dataKey) && !this._config.execution.continue) {
this._processCollision(dataKey, dataToSave);
}
TransactionStorage.set(dataKey, dataToSave, true);
return dataToSave;
}
private _saveContract(keyByArgs: string, dataToSave: ContractFieldsToSave) {
if (TransactionStorage.has(keyByArgs) && !this._config.execution.continue) {
this._processCollision(keyByArgs, dataToSave);
}
TransactionStorage.set(keyByArgs, dataToSave, true);
}
private _saveContractByName(contractName: string, dataToSave: ContractFieldsToSave) {
if (TransactionStorage.has(contractName) && !this._config.execution.continue) {
this._processCollision(contractName, dataToSave);
}
TransactionStorage.set(contractName, dataToSave, true);
}
private _processCollision(dataKey: string, dataToSave: TransactionFieldsToSave | ContractFieldsToSave) {
if (this._config.execution.continue) {
return;
}
const oldData: {
receipt?: TransactionReceiptParams;
contractAddress?: string;
metadata: MigrationMetadata;
} = TransactionStorage.get(dataKey);
if (oldData.contractAddress && isFullyQualifiedName(dataKey)) {
Reporter!.notifyContractCollisionByName(oldData as ContractFieldsToSave, dataToSave as ContractFieldsToSave);
return;
}
if (oldData.contractAddress) {
Reporter!.notifyContractCollisionByKeyFields(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 _tryGetDataFromStorage(key: string): any {
const value = TransactionStorage.get(key);
if (!value) {
throw new MigrateError(`Requested data not found in storage`);
}
return value;
}
}
export let TransactionProcessor: BaseTransactionProcessor | null = null;
export function createTransactionProcessor(config: MigrateConfig) {
if (TransactionProcessor) {
return;
}
TransactionProcessor = new BaseTransactionProcessor(config);
}