@solarity/hardhat-migrate
Version:
The simplest way to deploy smart contracts
321 lines (252 loc) • 11.3 kB
text/typescript
import { Interface, isAddress, ZeroAddress } from "ethers";
import { CatchClassError, getChainId, isDeployedContractAddress, MigrateError } from "../utils/index.js";
import { SEND_NATIVE_TX_NAME } from "../../constants.js";
import { Adapter } from "./adapters/Adapter.js";
import { BytecodeAdapter } from "./adapters/BytecodeAdapter.js";
import type { OverridesAndLibs } from "../../types/deployer.js";
import type { BaseInstance, Instance, TypedArgs } from "../../types/adapter.js";
import type { KeyTransactionFields, MigrationMetadata, TransactionFieldsToSave } from "../../types/tools.js";
import { isBytecodeFactory, isEthersContractFactory, isTypechainFactoryClass } from "../../types/index.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 { TransactionProcessor } from "../tools/storage/TransactionProcessor.js";
import { VerificationProcessor } from "../tools/storage/VerificationProcessor.js";
import { EthersContractFactoryAdapter } from "./adapters/EthersContractFactoryAdapter.js";
import { TypechainContractFactoryAdapter } from "./adapters/TypechainContractFactoryAdapter.js";
import { ExtendedHardhatEthersSigner } from "../tools/network/ExtendedHardhatEthersSigner.js";
import { connection, migratorConfig } from "../tools/network/EthersProvider.js";
import { NetworkConnection } from "hardhat/types/network";
export class Deployer {
constructor() {}
public static resolveAdapter<A, I = any>(contract: BaseInstance<A, I>): Adapter {
if (isTypechainFactoryClass(contract)) {
return new TypechainContractFactoryAdapter();
}
if (isEthersContractFactory(contract)) {
return new EthersContractFactoryAdapter();
}
if (isBytecodeFactory(contract)) {
return new BytecodeAdapter();
}
throw new MigrateError("Unknown Contract Factory Type");
}
public connection(): NetworkConnection<"generic"> {
return connection!;
}
public async deploy<A, I = any>(
contract: Instance<A, I>,
argsOrParameters: OverridesAndLibs | TypedArgs<A> = [] as TypedArgs<A>,
parameters: OverridesAndLibs = {},
): Promise<I> {
if (!Array.isArray(argsOrParameters)) {
parameters = argsOrParameters;
argsOrParameters = [] as TypedArgs<A>;
}
const adapter = Deployer.resolveAdapter(contract);
const minimalContract = await adapter.fromInstance(contract, parameters);
const contractAddress = await minimalContract.deploy(argsOrParameters as TypedArgs<A>, parameters);
return adapter.toInstance(contract, contractAddress, parameters);
}
public async deployERC1967Proxy<A, I = any>(
implementationFactory: Instance<A, I>,
constructorCalldata: string,
argsOrParameters: OverridesAndLibs | TypedArgs<A> = [] as TypedArgs<A>,
parameters: OverridesAndLibs = {},
) {
await this.validateConstructorCalldata(implementationFactory, parameters, constructorCalldata);
return this.deployProxy(
implementationFactory,
"ERC1967Proxy",
(implementationAddress) => [implementationAddress, constructorCalldata],
argsOrParameters,
parameters,
);
}
public async deployAdminableProxy<A, I = any>(
implementationFactory: Instance<A, I>,
constructorCalldata: string,
argsOrParameters: OverridesAndLibs | TypedArgs<A> = [] as TypedArgs<A>,
parameters: OverridesAndLibs = {},
) {
await this.validateConstructorCalldata(implementationFactory, parameters, constructorCalldata);
return this.deployProxy(
implementationFactory,
"AdminableProxy",
(implementationAddress) => [implementationAddress, constructorCalldata],
argsOrParameters,
parameters,
);
}
public async deployTransparentUpgradeableProxy<A, I = any>(
implementationFactory: Instance<A, I>,
proxyAdmin: string,
constructorCalldata: string,
argsOrParameters: OverridesAndLibs | TypedArgs<A> = [] as TypedArgs<A>,
parameters: OverridesAndLibs = {},
) {
await this.validateConstructorCalldata(implementationFactory, parameters, constructorCalldata);
if (proxyAdmin === ZeroAddress) {
throw new MigrateError("Proxy admin cannot be the zero address");
}
return this.deployProxy(
implementationFactory,
"TransparentUpgradeableProxy",
(implementationAddress) => [implementationAddress, proxyAdmin, constructorCalldata],
argsOrParameters,
parameters,
);
}
public async deployProxy<A, I = any>(
implementationFactory: Instance<A, I>,
proxyFactoryName: string,
proxyArgs: (implementationAddress: string) => any[],
argsOrParameters: OverridesAndLibs | TypedArgs<A> = [] as TypedArgs<A>,
parameters: OverridesAndLibs = {},
) {
let proxyFactory;
try {
proxyFactory = await connection!.ethers.getContractFactory(proxyFactoryName);
} catch {
throw new MigrateError(
`Contract factory for ${proxyFactoryName} not found. Please add import of the proxy to one of the contracts in the project.`,
);
}
const adapter = Deployer.resolveAdapter(implementationFactory);
let implementationArgs: TypedArgs<A> = [] as any;
let implementationParameters: OverridesAndLibs = {};
if (Array.isArray(argsOrParameters)) {
implementationArgs = argsOrParameters;
implementationParameters = parameters;
} else if (argsOrParameters && typeof argsOrParameters === "object") {
implementationParameters = { ...parameters, ...argsOrParameters };
} else {
implementationParameters = parameters;
}
let instanceName = adapter.getContractName(implementationFactory, implementationParameters);
const implementation = (await this.deploy(implementationFactory, implementationArgs, {
...implementationParameters,
name: `${instanceName} implementation`,
})) as any;
let implementationArtifact = adapter.getContractName(implementationFactory, {});
VerificationProcessor.saveVerificationFunction({
contractAddress: await implementation.getAddress(),
contractName: implementationArtifact,
constructorArguments: implementationArgs,
chainId: Number(await getChainId()),
});
const hre = await import("hardhat");
let artifact = await hre.artifacts.readArtifact(proxyFactoryName);
const proxy = await this.deploy(proxyFactory, proxyArgs(await implementation.getAddress()), {
name: `${instanceName} proxy`,
});
VerificationProcessor.saveVerificationFunction({
contractAddress: await proxy.getAddress(),
contractName: `${artifact.sourceName}:${artifact.contractName}`,
constructorArguments: proxyArgs(await implementation.getAddress()),
chainId: Number(await getChainId()),
});
return this.deployed(implementationFactory, `${instanceName} proxy`);
}
public async validateConstructorCalldata<A, I = any>(
implementationFactory: Instance<A, I>,
parameters: OverridesAndLibs,
constructorCalldata: string,
) {
if (!isTypechainFactoryClass(implementationFactory) || constructorCalldata == "0x" || constructorCalldata == "") {
return;
}
const adapter = Deployer.resolveAdapter(implementationFactory);
const contractInterface: Interface = adapter.getInterface(implementationFactory);
const functionFragment = contractInterface.parseTransaction({ data: constructorCalldata });
if (!functionFragment) {
throw new MigrateError("Invalid constructor calldata. Correct it and use --continue flag to continue migration.");
}
Reporter!.notifyOfProxyConstructorUsage(
adapter.getContractName(implementationFactory, parameters),
functionFragment.name,
);
}
public async deployed<A, I = any>(contract: BaseInstance<A, I>, contractIdentifier?: string): Promise<I> {
const adapter = Deployer.resolveAdapter(contract);
const defaultContractName = adapter.getContractName(contract, {});
let contractAddress;
if (contractIdentifier === undefined) {
contractAddress = await TransactionProcessor?.tryRestoreContractAddressByName(defaultContractName);
return adapter.toInstance(contract, contractAddress!, {});
}
if (isAddress(contractIdentifier)) {
if (!(await isDeployedContractAddress(contractIdentifier))) {
throw new MigrateError(`Contract with address '${contractIdentifier}' is not deployed`);
}
return adapter.toInstance(contract, contractIdentifier, {});
}
contractAddress = await TransactionProcessor?.tryRestoreContractAddressByName(contractIdentifier);
return adapter.toInstance(contract, contractAddress!, {});
}
public async save<A, I = any>(contract: Instance<A, I> | string, contractAddress: string, force: boolean = false) {
if (!(await isDeployedContractAddress(contractAddress)) && !force) {
throw new MigrateError(`Contract with address '${contractAddress}' is not deployed`);
}
const saveMetadata: MigrationMetadata = {
migrationNumber: Stats.currentMigration,
};
if (typeof contract === "string") {
TransactionProcessor?.saveContractAddress(contract, contractAddress, saveMetadata);
return;
}
const adapter = Deployer.resolveAdapter(contract);
const defaultContractName = adapter.getContractName(contract, {});
TransactionProcessor?.saveContractAddress(defaultContractName, contractAddress, saveMetadata);
}
public async sendNative(
to: string,
value: bigint,
name: string = SEND_NATIVE_TX_NAME,
): Promise<TransactionFieldsToSave> {
const signer = await networkManager!.getSigner();
const tx = await this._buildSendTransaction(to, value, name);
const methodString = "sendNative";
if (migratorConfig!.execution.continue) {
try {
const savedTx = TransactionProcessor?.tryRestoreSavedTransaction(tx);
Reporter!.notifyTransactionRecovery(methodString, savedTx!);
return savedTx!;
} catch {
Reporter!.notifyTransactionSendingInsteadOfRecovery(methodString);
}
}
const txResponse = await signer.sendTransaction(tx);
const [receipt] = await Promise.all([
txResponse.wait(migratorConfig!.execution.wait),
TransactionRunner!.reportTransactionResponse(txResponse, methodString),
]);
const saveMetadata: MigrationMetadata = {
migrationNumber: Stats.currentMigration,
methodName: methodString,
};
const savedTx = TransactionProcessor?.saveTransaction(tx, receipt!, saveMetadata);
return savedTx!;
}
public async setSigner(from?: string) {
await networkManager!.setSigner(from);
}
public async getSigner(from?: string): Promise<ExtendedHardhatEthersSigner> {
return networkManager!.getSigner(from);
}
public async getChainId(): Promise<bigint> {
return getChainId();
}
private async _buildSendTransaction(to: string, value: bigint, name: string): Promise<KeyTransactionFields> {
return {
to,
value,
chainId: await getChainId(),
data: "0x",
from: await (await networkManager!.getSigner()).getAddress(),
name,
};
}
}