UNPKG

@solarity/hardhat-migrate

Version:
314 lines (247 loc) 11.1 kB
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"; @catchError 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"); } }