@solarity/hardhat-migrate
Version:
The simplest way to deploy smart contracts
314 lines (247 loc) • 11.1 kB
text/typescript
import { Interface, isAddress, ZeroAddress } from "ethers";
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { catchError, getChainId, isDeployedContractAddress } from "../utils";
import { MigrateError } from "../errors";
import { SEND_NATIVE_TX_NAME } from "../constants";
import { Adapter } from "./adapters/Adapter";
import { BytecodeAdapter } from "./adapters/BytecodeAdapter";
import { OverridesAndLibs } from "../types/deployer";
import { BaseInstance, Instance, TypedArgs } from "../types/adapter";
import { KeyTransactionFields, MigrationMetadata, TransactionFieldsToSave } from "../types/tools";
import { isEthersContractFactory, isBytecodeFactory, isTypechainFactoryClass } from "../types/type-checks";
import { Stats } from "../tools/Stats";
import { Reporter } from "../tools/reporters/Reporter";
import { networkManager } from "../tools/network/NetworkManager";
import { TransactionRunner } from "../tools/runners/TransactionRunner";
import { TransactionProcessor } from "../tools/storage/TransactionProcessor";
import { VerificationProcessor } from "../tools/storage/VerificationProcessor";
import { EthersContractFactoryAdapter } from "./adapters/EthersContractFactoryAdapter";
import { TypechainContractFactoryAdapter } from "./adapters/TypechainContractFactoryAdapter";
import { ExtendedHardhatEthersSigner } from "../tools/network/ExtendedHardhatEthersSigner";
export class Deployer {
constructor(private _hre: HardhatRuntimeEnvironment) {}
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(this._hre, 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 = {},
) {
const { ethers, artifacts } = await import("hardhat");
let proxyFactory;
try {
proxyFactory = await 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(this._hre, implementationFactory);
let implementationArgs: TypedArgs<A> = [] as any;
let implementationParameters: OverridesAndLibs = {};
if (argsOrParameters && Array.isArray(argsOrParameters)) {
implementationArgs = argsOrParameters;
} else if (argsOrParameters && typeof argsOrParameters === "object") {
implementationParameters = argsOrParameters;
} else {
implementationParameters = parameters;
}
let instanceName = adapter.getContractName(implementationFactory, implementationParameters);
const implementation = (await this.deploy(implementationFactory, implementationArgs, {
...implementationParameters,
name: `${instanceName} implementation`,
})) as any;
VerificationProcessor.saveVerificationFunction({
contractAddress: await implementation.getAddress(),
contractName: instanceName,
constructorArguments: implementationArgs,
chainId: Number(await getChainId()),
});
let artifact = await 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(this._hre, 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(this._hre, 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(this._hre, 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 (this._hre.config.migrate.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(this._hre.config.migrate.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,
};
}
public static resolveAdapter<A, I = any>(hre: HardhatRuntimeEnvironment, contract: BaseInstance<A, I>): Adapter {
if (isTypechainFactoryClass(contract)) {
return new TypechainContractFactoryAdapter(hre);
}
if (isEthersContractFactory(contract)) {
return new EthersContractFactoryAdapter(hre);
}
if (isBytecodeFactory(contract)) {
return new BytecodeAdapter(hre);
}
throw new MigrateError("Unknown Contract Factory Type");
}
}