Skip to content

Commit

Permalink
Add public functions getContractFactoryFromArtifact/`getContractAtF…
Browse files Browse the repository at this point in the history
…romArtifact`

Allow passing of artifacts (resolved via custom code) into equivalents of `getContractFactory` and `getContractAt` calls, this is to support custom artifact resolution.

Relates to #1716.
  • Loading branch information
kanej committed Dec 7, 2021
1 parent ed4e146 commit 6fd363a
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 23 deletions.
5 changes: 5 additions & 0 deletions .changeset/smooth-worms-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nomiclabs/hardhat-ethers": patch
---

Add equivalents in hardhat-ethers for `getContractFactory` and `getContractAt` that support passing `Artifact`, specifically `getContractFactoryFromArtifact` and `getContractAtFromArtifact` (issue #1716)
3 changes: 3 additions & 0 deletions packages/hardhat-ethers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,12 @@ function getContractFactory(name: string, signer: ethers.Signer): Promise<ethers

function getContractFactory(name: string, factoryOptions: FactoryOptions): Promise<ethers.ContractFactory>;

function getContractFactoryFromArtifact(artifact: Artifact, signer?: ethers.Signer): Promise<ethers.ContractFactory>;

function getContractAt(nameOrAbi: string | any[], address: string, signer?: ethers.Signer): Promise<ethers.Contract>;

function getContractAtFromArtifact(artifact: Artifact, address: string, signer?: ethers.Signer): Promise<ethers.Contract>;

function getSigners() => Promise<ethers.Signer[]>;

function getSigner(address: string) => Promise<ethers.Signer>;
Expand Down
56 changes: 33 additions & 23 deletions packages/hardhat-ethers/src/internal/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,11 @@ export async function getContractFactory(
signer?: ethers.Signer
) {
if (typeof nameOrAbi === "string") {
return getContractFactoryByName(
const artifact = await hre.artifacts.readArtifact(nameOrAbi);

return getContractFactoryFromArtifact(
hre,
nameOrAbi,
artifact,
bytecodeOrFactoryOptions as ethers.Signer | FactoryOptions | undefined
);
}
Expand All @@ -92,13 +94,11 @@ function isFactoryOptions(
return true;
}

async function getContractFactoryByName(
export async function getContractFactoryFromArtifact(
hre: HardhatRuntimeEnvironment,
contractName: string,
artifact: Artifact,
signerOrOptions?: ethers.Signer | FactoryOptions
) {
const artifact = await hre.artifacts.readArtifact(contractName);

let libraries: Libraries = {};
let signer: ethers.Signer | undefined;
if (isFactoryOptions(signerOrOptions)) {
Expand All @@ -111,8 +111,8 @@ async function getContractFactoryByName(
if (artifact.bytecode === "0x") {
throw new NomicLabsHardhatPluginError(
pluginName,
`You are trying to create a contract factory for the contract ${contractName}, which is abstract and can't be deployed.
If you want to call a contract using ${contractName} as its interface use the "getContractAt" function instead.`
`You are trying to create a contract factory for the contract ${artifact.contractName}, which is abstract and can't be deployed.
If you want to call a contract using ${artifact.contractName} as its interface use the "getContractAt" function instead.`
);
}

Expand Down Expand Up @@ -265,26 +265,14 @@ export async function getContractAt(
address: string,
signer?: ethers.Signer
) {
const { Contract } = require("ethers") as typeof ethers;

if (typeof nameOrAbi === "string") {
const artifact = await hre.artifacts.readArtifact(nameOrAbi);
const factory = await getContractFactoryByAbiAndBytecode(
hre,
artifact.abi,
"0x",
signer
);

let contract = factory.attach(address);
// If there's no signer, we connect the contract instance to the provider for the selected network.
if (contract.provider === null) {
contract = contract.connect(hre.ethers.provider);
}

return contract;
return getContractAtFromArtifact(hre, artifact, address, signer);
}

const { Contract } = require("ethers") as typeof ethers;

if (signer === undefined) {
const signers = await hre.ethers.getSigners();
signer = signers[0];
Expand All @@ -303,6 +291,28 @@ export async function getContractAt(
return new Contract(address, abiWithAddedGas, signerOrProvider);
}

export async function getContractAtFromArtifact(
hre: HardhatRuntimeEnvironment,
artifact: Artifact,
address: string,
signer?: ethers.Signer
) {
const factory = await getContractFactoryByAbiAndBytecode(
hre,
artifact.abi,
"0x",
signer
);

let contract = factory.attach(address);
// If there's no signer, we connect the contract instance to the provider for the selected network.
if (contract.provider === null) {
contract = contract.connect(hre.ethers.provider);
}

return contract;
}

// This helper adds a `gas` field to the ABI function elements if the network
// is set up to use a fixed amount of gas.
// This is done so that ethers doesn't automatically estimate gas limits on
Expand Down
7 changes: 7 additions & 0 deletions packages/hardhat-ethers/src/internal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { lazyObject } from "hardhat/plugins";

import {
getContractAt,
getContractAtFromArtifact,
getContractFactory,
getContractFactoryFromArtifact,
getSigner,
getSigners,
} from "./helpers";
Expand Down Expand Up @@ -42,7 +44,12 @@ extendEnvironment((hre) => {
// We cast to any here as we hit a limitation of Function#bind and
// overloads. See: https://github.com/microsoft/TypeScript/issues/28582
getContractFactory: getContractFactory.bind(null, hre) as any,
getContractFactoryFromArtifact: getContractFactoryFromArtifact.bind(
null,
hre
),
getContractAt: getContractAt.bind(null, hre),
getContractAtFromArtifact: getContractAtFromArtifact.bind(null, hre),
};
});
});
10 changes: 10 additions & 0 deletions packages/hardhat-ethers/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type * as ethers from "ethers";
import { Artifact } from "hardhat/src/types";

import type { SignerWithAddress } from "../signers";

Expand All @@ -25,11 +26,20 @@ export interface HardhatEthersHelpers {
provider: ethers.providers.JsonRpcProvider;

getContractFactory: typeof getContractFactory;
getContractFactoryFromArtifact: (
artifact: Artifact,
signerOrOptions?: ethers.Signer | FactoryOptions
) => Promise<ethers.ContractFactory>;
getContractAt: (
nameOrAbi: string | any[],
address: string,
signer?: ethers.Signer
) => Promise<ethers.Contract>;
getContractAtFromArtifact: (
artifact: Artifact,
address: string,
signer?: ethers.Signer
) => Promise<ethers.Contract>;
getSigner: (address: string) => Promise<SignerWithAddress>;
getSigners: () => Promise<SignerWithAddress[]>;
}
131 changes: 131 additions & 0 deletions packages/hardhat-ethers/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,83 @@ describe("Ethers plugin", function () {
});
});

describe("getContractFactoryFromArtifact", function () {
it("should return a contract factory", async function () {
const contract = await this.env.ethers.getContractFactoryFromArtifact(
greeterArtifact
);

assert.containsAllKeys(contract.interface.functions, [
"setGreeting(string)",
"greet()",
]);

assert.equal(
await contract.signer.getAddress(),
await signers[0].getAddress()
);
});

it("should link a library", async function () {
const libraryFactory = await this.env.ethers.getContractFactory(
"TestLibrary"
);
const library = await libraryFactory.deploy();

const testContractLibArtifact = await this.env.artifacts.readArtifact(
"TestContractLib"
);

const contractFactory =
await this.env.ethers.getContractFactoryFromArtifact(
testContractLibArtifact,
{ libraries: { TestLibrary: library.address } }
);

assert.equal(
await contractFactory.signer.getAddress(),
await signers[0].getAddress()
);
const numberPrinter = await contractFactory.deploy();
const someNumber = 50;
assert.equal(
await numberPrinter.callStatic.printNumber(someNumber),
someNumber * 2
);
});

it("Should be able to send txs and make calls", async function () {
const Greeter = await this.env.ethers.getContractFactoryFromArtifact(
greeterArtifact
);
const greeter = await Greeter.deploy();

assert.equal(await greeter.functions.greet(), "Hi");
await greeter.functions.setGreeting("Hola");
assert.equal(await greeter.functions.greet(), "Hola");
});

describe("with custom signer", function () {
it("should return a contract factory connected to the custom signer", async function () {
const contract =
await this.env.ethers.getContractFactoryFromArtifact(
greeterArtifact,
signers[1]
);

assert.containsAllKeys(contract.interface.functions, [
"setGreeting(string)",
"greet()",
]);

assert.equal(
await contract.signer.getAddress(),
await signers[1].getAddress()
);
});
});
});

describe("getContractAt", function () {
let deployedGreeter: ethers.Contract;

Expand Down Expand Up @@ -780,6 +857,60 @@ describe("Ethers plugin", function () {
});
});
});

describe("getContractAtFromArtifact", function () {
let deployedGreeter: ethers.Contract;

beforeEach(async function () {
const Greeter = await this.env.ethers.getContractFactory("Greeter");
deployedGreeter = await Greeter.deploy();
});

describe("by artifact and address", function () {
it("Should return an instance of a contract", async function () {
const contract = await this.env.ethers.getContractAtFromArtifact(
greeterArtifact,
deployedGreeter.address
);

assert.containsAllKeys(contract.functions, [
"setGreeting(string)",
"greet()",
]);

assert.equal(
await contract.signer.getAddress(),
await signers[0].getAddress()
);
});

it("Should be able to send txs and make calls", async function () {
const greeter = await this.env.ethers.getContractAtFromArtifact(
greeterArtifact,
deployedGreeter.address
);

assert.equal(await greeter.functions.greet(), "Hi");
await greeter.functions.setGreeting("Hola");
assert.equal(await greeter.functions.greet(), "Hola");
});

describe("with custom signer", function () {
it("Should return an instance of a contract associated to a custom signer", async function () {
const contract = await this.env.ethers.getContractAtFromArtifact(
greeterArtifact,
deployedGreeter.address,
signers[1]
);

assert.equal(
await contract.signer.getAddress(),
await signers[1].getAddress()
);
});
});
});
});
});
});

Expand Down

0 comments on commit 6fd363a

Please sign in to comment.