diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c8c5bf..b1c96dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Version 2.1.10 + +* Added an ability to change signer during the migration + ## Version 2.1.9 * Updated dependencies diff --git a/README.md b/README.md index bacc2ab..b99ecf2 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,14 @@ Facilitates sending native assets to a specified address, primarily for the reco --- +- **setSigner(from <- optional)**: + +Sets the signer for the following transactions and deployments. + +If the `from` parameter is not specified, the signer is reset to the default. + +--- + - **getSigner(from <- optional)**: Retrieves an ethers signer for use in migrations. diff --git a/package-lock.json b/package-lock.json index 6f2906f..086312b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@solarity/hardhat-migrate", - "version": "2.1.8", + "version": "2.1.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@solarity/hardhat-migrate", - "version": "2.1.8", + "version": "2.1.10", "license": "MIT", "workspaces": [ "test/fixture-projects/*" diff --git a/package.json b/package.json index 830f4a4..48d9d26 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@solarity/hardhat-migrate", - "version": "2.1.9", + "version": "2.1.10", "description": "Automatic deployment and verification of smart contracts", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", diff --git a/src/deployer/Deployer.ts b/src/deployer/Deployer.ts index 04d4a08..52cab11 100644 --- a/src/deployer/Deployer.ts +++ b/src/deployer/Deployer.ts @@ -2,7 +2,7 @@ import { isAddress, Signer } from "ethers"; import { HardhatRuntimeEnvironment } from "hardhat/types"; -import { catchError, getChainId, getSignerHelper, isDeployedContractAddress } from "../utils"; +import { catchError, getChainId, isDeployedContractAddress } from "../utils"; import { MigrateError } from "../errors"; @@ -21,6 +21,7 @@ import { isContractFactory, isEthersContract, isBytecodeFactory, isTruffleFactor 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"; @@ -103,7 +104,7 @@ export class Deployer { value: bigint, name: string = SEND_NATIVE_TX_NAME, ): Promise { - const signer = await getSignerHelper(); + const signer = await networkManager!.getSigner(); const tx = await this._buildSendTransaction(to, value, name); @@ -138,8 +139,12 @@ export class Deployer { return savedTx!; } + public async setSigner(from?: string) { + await networkManager!.setSigner(from); + } + public async getSigner(from?: string): Promise { - return getSignerHelper(from); + return networkManager!.getSigner(from); } public async getChainId(): Promise { @@ -152,7 +157,7 @@ export class Deployer { value, chainId: await getChainId(), data: "0x", - from: (await getSignerHelper()).address, + from: (await networkManager!.getSigner()).address, name, }; } diff --git a/src/deployer/MinimalContract.ts b/src/deployer/MinimalContract.ts index 4a2bf25..e0cd073 100644 --- a/src/deployer/MinimalContract.ts +++ b/src/deployer/MinimalContract.ts @@ -6,7 +6,7 @@ import { isFullyQualifiedName } from "hardhat/utils/contract-names"; import { Linker } from "./Linker"; -import { catchError, fillParameters, getChainId, getInterfaceOnlyWithConstructor, getSignerHelper } from "../utils"; +import { catchError, fillParameters, getChainId, getInterfaceOnlyWithConstructor } from "../utils"; import { MigrateError } from "../errors"; @@ -15,6 +15,7 @@ import { ContractDeployTxWithName, OverridesAndLibs } from "../types/deployer"; import { Stats } from "../tools/Stats"; import { Reporter } from "../tools/reporters/Reporter"; +import { networkManager } from "../tools/network/NetworkManager"; import { TransactionRunner } from "../tools/runners/TransactionRunner"; import { ArtifactProcessor } from "../tools/storage/ArtifactProcessor"; import { TransactionProcessor } from "../tools/storage/TransactionProcessor"; @@ -77,7 +78,7 @@ export class MinimalContract { return { contractName: this._contractName, chainId: await getChainId(), - from: (await getSignerHelper(txOverrides.from)).address, + from: (await networkManager!.getSigner(txOverrides.from)).address, ...(await factory.getDeployTransaction(...args, txOverrides)), }; } @@ -101,7 +102,7 @@ export class MinimalContract { } private async _processContractDeploymentTransaction(tx: ContractDeployTxWithName, args: any[]): Promise { - const signer: Signer = await getSignerHelper(tx.from); + const signer: Signer = await networkManager!.getSigner(tx.from); const txResponse = await signer.sendTransaction(tx); diff --git a/src/deployer/adapters/AbstractEthersAdapter.ts b/src/deployer/adapters/AbstractEthersAdapter.ts index 768ead1..970fbc5 100644 --- a/src/deployer/adapters/AbstractEthersAdapter.ts +++ b/src/deployer/adapters/AbstractEthersAdapter.ts @@ -18,7 +18,7 @@ import "../../type-extensions"; import { UNKNOWN_TRANSACTION_NAME } from "../../constants"; -import { bytecodeToString, fillParameters, getMethodString, getSignerHelper } from "../../utils"; +import { bytecodeToString, fillParameters, getMethodString } from "../../utils"; import { OverridesAndLibs, OverridesAndName } from "../../types/deployer"; import { EthersContract, BytecodeFactory } from "../../types/adapter"; @@ -26,6 +26,7 @@ import { KeyTransactionFields, MigrationMetadata, TransactionFieldsToSave } from 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"; @@ -46,7 +47,7 @@ export abstract class AbstractEthersAdapter extends Adapter { } public async toInstance(instance: Factory, address: string, parameters: OverridesAndLibs): Promise { - const signer = await getSignerHelper(parameters.from); + const signer = await networkManager!.getSigner(parameters.from); const contractName = this.getContractName(instance, parameters); let contract = new BaseContract(address, this.getInterface(instance), signer); @@ -102,7 +103,7 @@ export abstract class AbstractEthersAdapter extends Adapter { contractInterface: Interface, contractName: string, ): Promise { - const defaultRunner = await getSignerHelper(); + const defaultRunner = await networkManager!.getSigner(); contract.connect = (runner: ContractRunner | null): BaseContract => { const newContract = new BaseContract(contract.target, contractInterface, runner ?? defaultRunner); diff --git a/src/deployer/adapters/TruffleAdapter.ts b/src/deployer/adapters/TruffleAdapter.ts index cdcae0b..a36a3b6 100644 --- a/src/deployer/adapters/TruffleAdapter.ts +++ b/src/deployer/adapters/TruffleAdapter.ts @@ -14,14 +14,7 @@ import { Adapter } from "./Adapter"; import { MinimalContract } from "../MinimalContract"; -import { - bytecodeToString, - catchError, - fillParameters, - getInstanceNameFromClass, - getMethodString, - getSignerHelper, -} from "../../utils"; +import { bytecodeToString, catchError, fillParameters, getInstanceNameFromClass, getMethodString } from "../../utils"; import { UNKNOWN_TRANSACTION_NAME } from "../../constants"; @@ -31,6 +24,7 @@ import { OverridesAndName, TruffleTransactionResponse } from "../../types/deploy import { Stats } from "../../tools/Stats"; import { Reporter } from "../../tools/reporters/Reporter"; +import { networkManager } from "../../tools/network/NetworkManager"; import { TransactionRunner } from "../../tools/runners/TransactionRunner"; import { ArtifactProcessor } from "../../tools/storage/ArtifactProcessor"; import { TransactionProcessor } from "../../tools/storage/TransactionProcessor"; @@ -133,14 +127,14 @@ export class TruffleAdapter extends Adapter { const tx = await contractMethod.populateTransaction(...args); // In case if the `from` field is not specified, it should be filled with the default signer. - tx.from = tx.from ?? (await getSignerHelper()).address; + tx.from = tx.from ?? (await networkManager!.getSigner()).address; await fillParameters(tx); const keyFields = this._getKeyFieldsFromTransaction(tx, contractMethod, args); // Connect to signer and get method again with signer - contractMethod = ethersBaseContract.connect(await getSignerHelper(tx.from)).getFunction(methodName); + contractMethod = ethersBaseContract.connect(await networkManager!.getSigner(tx.from)).getFunction(methodName); const methodString = getMethodString(contractName, methodName, contractMethod.fragment, args); diff --git a/src/tools/network/NetworkManager.ts b/src/tools/network/NetworkManager.ts index 4644aca..7092e89 100644 --- a/src/tools/network/NetworkManager.ts +++ b/src/tools/network/NetworkManager.ts @@ -1,5 +1,7 @@ import axios, { Axios } from "axios"; +import { AddressLike, ethers } from "ethers"; +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import type { HardhatEthersProvider as HardhatEthersProviderT } from "@nomicfoundation/hardhat-ethers/internal/hardhat-ethers-provider"; import { HardhatRuntimeEnvironment } from "hardhat/types"; @@ -61,11 +63,27 @@ class NetworkManager { public axios: Axios; public provider: HardhatEthersProviderT; + private currentFrom: string | undefined = undefined; + constructor() { this.axios = this.withRetry(axios); this.provider = this.withRetry(ethersProvider!); } + public async getSigner(from?: null | AddressLike): Promise { + if (!from) { + return this.provider.getSigner(this.currentFrom); + } + + const address = await ethers.resolveAddress(from, this.provider); + + return this.provider.getSigner(address); + } + + public async setSigner(from?: AddressLike): Promise { + this.currentFrom = from ? await ethers.resolveAddress(from, this.provider) : undefined; + } + public withRetry(instance: T): T { return new Proxy(instance, { get(target, propKey, receiver) { diff --git a/src/utils/network.ts b/src/utils/network.ts index 148d785..cf504b9 100644 --- a/src/utils/network.ts +++ b/src/utils/network.ts @@ -1,6 +1,4 @@ -import { AddressLike, ethers, toBigInt } from "ethers"; - -import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; +import { toBigInt } from "ethers"; import { networkManager } from "../tools/network/NetworkManager"; @@ -11,13 +9,3 @@ export async function getChainId(): Promise { export async function isDeployedContractAddress(address: string): Promise { return (await networkManager!.provider.getCode(address)) !== "0x"; } - -export async function getSignerHelper(from?: null | AddressLike): Promise { - if (!from) { - return networkManager!.provider.getSigner(); - } - - const address = await ethers.resolveAddress(from, networkManager!.provider); - - return networkManager!.provider.getSigner(address); -} diff --git a/test/fixture-projects/hardhat-project-typechain-ethers/deploy/4_different.signers.migration.ts b/test/fixture-projects/hardhat-project-typechain-ethers/deploy/4_different.signers.migration.ts new file mode 100644 index 0000000..0047f6a --- /dev/null +++ b/test/fixture-projects/hardhat-project-typechain-ethers/deploy/4_different.signers.migration.ts @@ -0,0 +1,25 @@ +import { Deployer } from "../../../../src/deployer/Deployer"; + +import { GovToken__factory } from "../typechain-types"; + +export = async (deployer: Deployer) => { + await deployer.setSigner("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"); + + await deployer.deploy(GovToken__factory, ["Token", "TKN"], { name: "Governance Token #12" }); + + const govToken5 = await deployer.deployed(GovToken__factory, "Governance Token #12"); + + if ((await govToken5.owner()) !== "0x70997970C51812dc3A010C7d01b50e0d17dc79C8") { + console.error("Owner is not set correctly"); + process.exit(1); + } + + await deployer.setSigner(); + + const newToken = await deployer.deploy(GovToken__factory, ["Token", "TKN"], { name: "Governance Token #13" }); + + if ((await newToken.owner()) !== "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") { + console.error("Owner is not set correctly"); + process.exit(1); + } +}; diff --git a/test/fixture-projects/hardhat-project-typechain-truffle/deploy/4_different.signers.migration.ts b/test/fixture-projects/hardhat-project-typechain-truffle/deploy/4_different.signers.migration.ts new file mode 100644 index 0000000..30aed44 --- /dev/null +++ b/test/fixture-projects/hardhat-project-typechain-truffle/deploy/4_different.signers.migration.ts @@ -0,0 +1,27 @@ +import { Deployer } from "../../../../src/deployer/Deployer"; + +export = async (deployer: Deployer) => { + // Moved here for testing reasons, before it was initialized globally once and failed on the second test on the .link function. + // Because Truffle throws an error if attempting to link the same object twice to the same library. + const GovToken = artifacts.require("GovToken"); + + await deployer.setSigner("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"); + + await deployer.deploy(GovToken, ["Token", "TKN"], { name: "Governance Token #12" }); + + const govToken5 = await deployer.deployed(GovToken, "Governance Token #12"); + + if ((await govToken5.owner()) !== "0x70997970C51812dc3A010C7d01b50e0d17dc79C8") { + console.error("Owner is not set correctly"); + process.exit(1); + } + + await deployer.setSigner(); + + const newToken = await deployer.deploy(GovToken, ["Token", "TKN"], { name: "Governance Token #13" }); + + if ((await newToken.owner()) !== "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") { + console.error("Owner is not set correctly"); + process.exit(1); + } +}; diff --git a/test/integration/migration/typechain-ethers.test.ts b/test/integration/migration/typechain-ethers.test.ts index 4a35fe8..c738406 100644 --- a/test/integration/migration/typechain-ethers.test.ts +++ b/test/integration/migration/typechain-ethers.test.ts @@ -47,4 +47,14 @@ describe("typechain-ethers", () => { await runWithContinue(hre, 3); }); }); + + describe("migration flow for edge cases", () => { + it("should run migration successfully", async function () { + await runWithoutContinue(hre, 4); + }); + + it("should recover migration successfully", async function () { + await runWithContinue(hre, 4); + }); + }); }); diff --git a/test/integration/migration/typechain-truffle.test.ts b/test/integration/migration/typechain-truffle.test.ts index 04bee1d..cb9cd54 100644 --- a/test/integration/migration/typechain-truffle.test.ts +++ b/test/integration/migration/typechain-truffle.test.ts @@ -42,4 +42,14 @@ describe("typechain-truffle", () => { await runWithContinue(hre, 3); }); }); + + describe("migration flow for different signers", () => { + it("should run migration successfully", async function () { + await runWithoutContinue(hre, 4); + }); + + it("should recover migration successfully", async function () { + await runWithContinue(hre, 4); + }); + }); });