Skip to content

Commit

Permalink
Add overload to getContractFactory/getContractAt that accepts `Ar…
Browse files Browse the repository at this point in the history
…tifact`s

Allow passing of artifacts (resolved via custom code) into the `getContractFactory` and `getContractAt` calls, this is instead of resolving by contract name.

Relates to #1716.
  • Loading branch information
kanej committed Dec 1, 2021
1 parent 7b0b31a commit 22fbd89
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 52 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 overloads for `Artifact` in hardhat-ethers for `getContractFactory` and `getContractAt` (issue #1716)
25 changes: 17 additions & 8 deletions packages/hardhat-ethers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,23 @@ interface FactoryOptions {
libraries?: Libraries;
}

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

function getContractFactory(name: string, signer: ethers.Signer): Promise<ethers.ContractFactory>;

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


function getContractAt(nameOrAbi: string | any[], address: string, signer?: ethers.Signer): Promise<ethers.Contract>;
function getContractFactory(
nameOrArtifact: string | Artifact,
signerOrOptions?: ethers.Signer | FactoryOptions
): Promise<ethers.ContractFactory>;

function getContractFactory(
abi: any[],
bytecode: ethers.utils.BytesLike,
signer?: ethers.Signer
): Promise<ethers.ContractFactory>;

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

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

Expand Down
131 changes: 89 additions & 42 deletions packages/hardhat-ethers/src/internal/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,28 @@ interface Link {

const pluginName = "hardhat-ethers";

function isArtifact(artifact: any): artifact is Artifact {
const {
contractName,
sourceName,
abi,
bytecode,
deployedBytecode,
linkReferences,
deployedLinkReferences,
} = artifact;

return (
typeof contractName === "string" &&
typeof sourceName === "string" &&
Array.isArray(abi) &&
typeof bytecode === "string" &&
typeof deployedBytecode === "string" &&
linkReferences !== undefined &&
deployedLinkReferences !== undefined
);
}

export async function getSigners(
hre: HardhatRuntimeEnvironment
): Promise<SignerWithAddress[]> {
Expand Down Expand Up @@ -46,7 +68,7 @@ export async function getSigner(

export function getContractFactory(
hre: HardhatRuntimeEnvironment,
name: string,
nameOrArtifact: string | Artifact,
signerOrOptions?: ethers.Signer | FactoryOptions
): Promise<ethers.ContractFactory>;

Expand All @@ -59,24 +81,40 @@ export function getContractFactory(

export async function getContractFactory(
hre: HardhatRuntimeEnvironment,
nameOrAbi: string | any[],
bytecodeOrFactoryOptions?:
nameOrArtifactOrAbi: string | Artifact | any[],
signerOrFactoryOptionsOrBytecode?:
| (ethers.Signer | FactoryOptions)
| ethers.utils.BytesLike,
signer?: ethers.Signer
) {
if (typeof nameOrAbi === "string") {
return getContractFactoryByName(
if (typeof nameOrArtifactOrAbi === "string") {
const artifact = await hre.artifacts.readArtifact(nameOrArtifactOrAbi);

return getContractFactoryByArtifact(
hre,
artifact,
signerOrFactoryOptionsOrBytecode as
| ethers.Signer
| FactoryOptions
| undefined
);
}

if (isArtifact(nameOrArtifactOrAbi)) {
return getContractFactoryByArtifact(
hre,
nameOrAbi,
bytecodeOrFactoryOptions as ethers.Signer | FactoryOptions | undefined
nameOrArtifactOrAbi,
signerOrFactoryOptionsOrBytecode as
| ethers.Signer
| FactoryOptions
| undefined
);
}

return getContractFactoryByAbiAndBytecode(
hre,
nameOrAbi,
bytecodeOrFactoryOptions as ethers.utils.BytesLike,
nameOrArtifactOrAbi,
signerOrFactoryOptionsOrBytecode as ethers.utils.BytesLike,
signer
);
}
Expand All @@ -92,13 +130,11 @@ function isFactoryOptions(
return true;
}

async function getContractFactoryByName(
async function getContractFactoryByArtifact(
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 +147,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 @@ -261,46 +297,57 @@ async function getContractFactoryByAbiAndBytecode(

export async function getContractAt(
hre: HardhatRuntimeEnvironment,
nameOrAbi: string | any[],
nameOrArtifactOrAbi: string | Artifact | any[],
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);
if (Array.isArray(nameOrArtifactOrAbi)) {
if (signer === undefined) {
const signers = await hre.ethers.getSigners();
signer = signers[0];
}

return contract;
}
// If there's no signer, we want to put the provider for the selected network here.
// This allows read only operations on the contract interface.
const signerOrProvider: ethers.Signer | ethers.providers.Provider =
signer !== undefined ? signer : hre.ethers.provider;

if (signer === undefined) {
const signers = await hre.ethers.getSigners();
signer = signers[0];
const abiWithAddedGas = addGasToAbiMethodsIfNecessary(
hre.network.config,
nameOrArtifactOrAbi
);

return new Contract(address, abiWithAddedGas, signerOrProvider);
}

// If there's no signer, we want to put the provider for the selected network here.
// This allows read only operations on the contract interface.
const signerOrProvider: ethers.Signer | ethers.providers.Provider =
signer !== undefined ? signer : hre.ethers.provider;
let artifact: Artifact;
if (typeof nameOrArtifactOrAbi === "string") {
artifact = await hre.artifacts.readArtifact(nameOrArtifactOrAbi);
} else if (isArtifact(nameOrArtifactOrAbi)) {
artifact = nameOrArtifactOrAbi;
} else {
throw new NomicLabsHardhatPluginError(
pluginName,
`Tried to get contract at address, but passed bad paramater ${nameOrArtifactOrAbi}, must be one of name or artifact or ABI.`
);
}

const abiWithAddedGas = addGasToAbiMethodsIfNecessary(
hre.network.config,
nameOrAbi
const factory = await getContractFactoryByAbiAndBytecode(
hre,
artifact.abi,
"0x",
signer
);

return new Contract(address, abiWithAddedGas, signerOrProvider);
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
Expand Down
6 changes: 4 additions & 2 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 @@ -12,9 +13,10 @@ export interface FactoryOptions {
}

export declare function getContractFactory(
name: string,
nameOrArtifact: string | Artifact,
signerOrOptions?: ethers.Signer | FactoryOptions
): Promise<ethers.ContractFactory>;

export declare function getContractFactory(
abi: any[],
bytecode: ethers.utils.BytesLike,
Expand All @@ -26,7 +28,7 @@ export interface HardhatEthersHelpers {

getContractFactory: typeof getContractFactory;
getContractAt: (
nameOrAbi: string | any[],
nameOrArtifactOrAbi: string | Artifact | any[],
address: string,
signer?: ethers.Signer
) => Promise<ethers.Contract>;
Expand Down
119 changes: 119 additions & 0 deletions packages/hardhat-ethers/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,80 @@ describe("Ethers plugin", function () {
});
});

describe("by Artifact", function () {
it("should return a contract factory", async function () {
const contract = await this.env.ethers.getContractFactory(
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.getContractFactory(
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.getContractFactory(
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.getContractFactory(
greeterArtifact,
signers[1]
);

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

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

describe("by abi and bytecode", function () {
it("should return a contract factory", async function () {
// It's already compiled in artifacts/
Expand Down Expand Up @@ -590,6 +664,51 @@ describe("Ethers plugin", function () {
});
});

describe("by artifact and address", function () {
it("Should return an instance of a contract", async function () {
const contract = await this.env.ethers.getContractAt(
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.getContractAt(
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.getContractAt(
greeterArtifact,
deployedGreeter.address,
signers[1]
);

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

describe("by abi and address", function () {
it("Should return an instance of a contract", async function () {
const contract = await this.env.ethers.getContractAt(
Expand Down

0 comments on commit 22fbd89

Please sign in to comment.