@solarity/hardhat-migrate
Version:
The simplest way to deploy smart contracts
163 lines (122 loc) • 5.67 kB
text/typescript
import { ethers, Interface, Overrides } from "ethers";
import { isFullyQualifiedName } from "hardhat/utils/contract-names";
import { Linker } from "./Linker.js";
import { CatchClassError, fillParameters, getChainId, getInterfaceOnlyWithConstructor } from "../utils/index.js";
import { MigrateError } from "../utils/index.js";
import type { MigrationMetadata } from "../../types/tools.js";
import type { ContractDeployTxWithName, OverridesAndLibs } from "../../types/deployer.js";
import { Stats } from "../tools/Stats.js";
import { Reporter } from "../tools/reporters/Reporter.js";
import { networkManager } from "../tools/network/NetworkManager.js";
import { TransactionRunner } from "../tools/runners/TransactionRunner.js";
import { ArtifactProcessor } from "../tools/storage/ArtifactProcessor.js";
import { TransactionProcessor } from "../tools/storage/TransactionProcessor.js";
import { VerificationProcessor } from "../tools/storage/VerificationProcessor.js";
import { migratorConfig } from "../tools/network/EthersProvider.js";
export class MinimalContract {
private readonly _rawBytecode: string;
private readonly _interfaceOnlyWithConstructor;
constructor(
private _bytecode: string,
private readonly _interface: Interface,
private readonly _contractName: string = "",
) {
this._interfaceOnlyWithConstructor = getInterfaceOnlyWithConstructor(this._interface.fragments);
this._rawBytecode = this._bytecode;
if (_contractName === "") {
try {
this._contractName = ArtifactProcessor.tryGetContractName(_bytecode);
} catch {
throw new MigrateError("Contract name is not provided and cannot be extracted from bytecode.");
}
}
}
public async deploy(args: any[] = [], parameters: OverridesAndLibs = {}): Promise<string> {
await fillParameters(parameters);
await this._tryLinkLibraries(parameters);
const tx = await this._createDeployTransaction(args, parameters);
if (migratorConfig!.execution.continue) {
return this._recoverContractAddress(tx, args);
} else {
return this._processContractDeploymentTransaction(tx, args);
}
}
private async _tryLinkLibraries(parameters: OverridesAndLibs): Promise<void> {
try {
if (!Linker.isBytecodeNeedsLinking(this._bytecode)) {
return;
}
this._bytecode = (await Linker.tryLinkBytecode(this._contractName, this._bytecode, parameters.libraries || {}))!;
} catch (e: any) {
throw new MigrateError(
`Unable to link libraries for ${this._contractName}! Try manually deploy the libraries and link them.\n Error: ${e.message}`,
);
}
}
private async _createDeployTransaction(args: any[], txOverrides: Overrides): Promise<ContractDeployTxWithName> {
const factory = new ethers.ContractFactory(this._interfaceOnlyWithConstructor, this._bytecode);
return {
contractName: this._contractName,
chainId: await getChainId(),
from: await (await networkManager!.getSigner(txOverrides.from)).getAddress(),
...(await factory.getDeployTransaction(...args, txOverrides)),
};
}
private async _recoverContractAddress(tx: ContractDeployTxWithName, args: any[]): Promise<string> {
try {
const contractAddress = await TransactionProcessor?.tryRestoreContractAddressByKeyFields(tx);
Reporter!.notifyContractRecovery(tx.contractName, contractAddress!);
await this._saveContractForVerification(contractAddress!, tx, args);
return contractAddress!;
} catch {
/* empty */
}
Reporter!.notifyDeploymentInsteadOfRecovery(tx.contractName);
return this._processContractDeploymentTransaction(tx, args);
}
private async _processContractDeploymentTransaction(tx: ContractDeployTxWithName, args: any[]): Promise<string> {
const signer = await networkManager!.getSigner(tx.from);
const txResponse = await signer.sendTransaction(tx);
await TransactionRunner!.reportTransactionResponse(txResponse, tx.contractName);
const contractAddress = (await txResponse.wait(0))!.contractAddress;
if (typeof contractAddress !== "string") {
throw new MigrateError("Contract deployment failed. Invalid contract address conversion.");
}
await this._saveContractForVerification(contractAddress, tx, args);
const saveMetadata: MigrationMetadata = {
migrationNumber: Stats.currentMigration,
contractName: tx.contractName,
fullyQualifiedContractName: this._getFullyQualifiedName(tx) || undefined,
};
TransactionProcessor?.saveDeploymentTransaction(tx, tx.contractName, contractAddress, saveMetadata);
return contractAddress;
}
private async _saveContractForVerification(contractAddress: string, tx: ContractDeployTxWithName, args: any[]) {
if (VerificationProcessor.isVerificationDataSaved(contractAddress)) {
return;
}
const contractName = this._getFullyQualifiedName(tx);
if (contractName === null) {
Reporter!.reportVerificationFailedToSave(tx.contractName);
return;
}
VerificationProcessor.saveVerificationFunction({
contractAddress,
contractName: contractName,
constructorArguments: args,
chainId: Number(await getChainId()),
});
await ArtifactProcessor.saveArtifactIfNotExist(contractName, this._rawBytecode);
}
private _getFullyQualifiedName(tx: ContractDeployTxWithName): string | null {
try {
if (!isFullyQualifiedName(tx.contractName)) {
return ArtifactProcessor.tryGetContractName(this._rawBytecode);
}
return tx.contractName;
} catch {
return null;
}
}
}