@nomicfoundation/hardhat-ethers
Version:
Hardhat plugin for ethers
260 lines • 12 kB
JavaScript
import { assertHardhatInvariant, HardhatError, } from "@nomicfoundation/hardhat-errors";
export class HardhatHelpers {
#provider;
#networkName;
#networkConfig;
#artifactManager;
constructor(provider, networkName, networkConfig, artifactManager) {
this.#provider = provider;
this.#networkName = networkName;
this.#networkConfig = networkConfig;
this.#artifactManager = artifactManager;
}
async getSigners() {
let accounts;
try {
accounts = await this.#provider.send("eth_accounts", []);
}
catch (error) {
if (error instanceof Error &&
/the method has been deprecated: eth_accounts/.test(error.message)) {
return [];
}
throw error;
}
const signersWithAddress = await Promise.all(accounts.map((account) => this.getSigner(account)));
return signersWithAddress;
}
async getSigner(address) {
const { HardhatEthersSigner: SignerWithAddressImpl } = await import("../signers/signers.js");
const signerWithAddress = await SignerWithAddressImpl.create(this.#provider, this.#networkName, this.#networkConfig, address);
return signerWithAddress;
}
async getContractFactory(nameOrAbi, bytecodeOrFactoryOptions, signer) {
if (typeof nameOrAbi === "string") {
const artifact = await this.#artifactManager.readArtifact(nameOrAbi);
return this.getContractFactoryFromArtifact(artifact,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- bytecodeOrFactoryOptions overlaps with one of the following types
bytecodeOrFactoryOptions);
}
assertHardhatInvariant(typeof bytecodeOrFactoryOptions === "string" ||
bytecodeOrFactoryOptions instanceof Uint8Array, "bytecode should be a string");
return this.#getContractFactoryByAbiAndBytecode(nameOrAbi, bytecodeOrFactoryOptions, signer);
}
async getContractFactoryFromArtifact(artifact, signerOrOptions) {
let libraries = {};
let signer;
if (!this.#isArtifact(artifact)) {
throw new HardhatError(HardhatError.ERRORS.HARDHAT_ETHERS.GENERAL.INVALID_ARTIFACT_FOR_FACTORY);
}
if (this.#isFactoryOptions(signerOrOptions)) {
signer = signerOrOptions.signer;
libraries = signerOrOptions.libraries ?? {};
}
else {
signer = signerOrOptions;
}
if (artifact.bytecode === "0x") {
throw new HardhatError(HardhatError.ERRORS.HARDHAT_ETHERS.GENERAL.INVALID_ABSTRACT_CONTRACT_FOR_FACTORY, { contractName: artifact.contractName });
}
const linkedBytecode = await this.#collectLibrariesAndLink(artifact, libraries);
return this.#getContractFactoryByAbiAndBytecode(artifact.abi, linkedBytecode, signer);
}
async getContractAt(nameOrAbi, address, signer) {
if (typeof nameOrAbi === "string") {
const artifact = await this.#artifactManager.readArtifact(nameOrAbi);
return this.getContractAtFromArtifact(artifact, address, signer);
}
const ethers = await import("ethers");
if (signer === undefined) {
const signers = await this.getSigners();
signer = signers[0];
}
// 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 = signer !== undefined ? signer : this.#provider;
let resolvedAddress;
if (ethers.isAddressable(address)) {
resolvedAddress = await address.getAddress();
}
else {
resolvedAddress = address;
}
return new ethers.Contract(resolvedAddress, nameOrAbi, signerOrProvider);
}
async getContractAtFromArtifact(artifact, address, signer) {
const ethers = await import("ethers");
if (!this.#isArtifact(artifact)) {
throw new HardhatError(HardhatError.ERRORS.HARDHAT_ETHERS.GENERAL.INVALID_ARTIFACT_FOR_FACTORY);
}
if (signer === undefined) {
const signers = await this.getSigners();
signer = signers[0];
}
let resolvedAddress;
if (ethers.isAddressable(address)) {
resolvedAddress = await address.getAddress();
}
else {
resolvedAddress = address;
}
let contract = new ethers.Contract(resolvedAddress, artifact.abi, signer);
if (contract.runner === null) {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- EthersT.Contract overlaps with EthersT.BaseContract
contract = contract.connect(this.#provider);
}
return contract;
}
async deployContract(name, argsOrSignerOrOptions, signerOrOptions) {
let args = [];
if (Array.isArray(argsOrSignerOrOptions)) {
args = argsOrSignerOrOptions;
}
else {
signerOrOptions = argsOrSignerOrOptions;
}
let overrides = {};
if (signerOrOptions !== undefined && !("getAddress" in signerOrOptions)) {
const overridesAndFactoryOptions = { ...signerOrOptions };
// we delete the factory options properties in case ethers
// rejects unknown properties
delete overridesAndFactoryOptions.signer;
delete overridesAndFactoryOptions.libraries;
overrides = overridesAndFactoryOptions;
}
const factory = await this.getContractFactory(name, signerOrOptions);
return factory.deploy(...args, overrides);
}
async getImpersonatedSigner(address) {
await this.#provider.send("hardhat_impersonateAccount", [address]);
return this.getSigner(address);
}
#isArtifact(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);
}
#isFactoryOptions(signerOrOptions) {
if (signerOrOptions === undefined || "provider" in signerOrOptions) {
return false;
}
return true;
}
async #collectLibrariesAndLink(artifact, libraries) {
const ethers = await import("ethers");
const neededLibraries = [];
for (const [sourceName, sourceLibraries] of Object.entries(artifact.linkReferences)) {
for (const libName of Object.keys(sourceLibraries)) {
neededLibraries.push({ sourceName, libName });
}
}
const linksToApply = new Map();
for (const [linkedLibraryName, linkedLibraryAddress] of Object.entries(libraries)) {
let resolvedAddress;
if (ethers.isAddressable(linkedLibraryAddress)) {
resolvedAddress = await linkedLibraryAddress.getAddress();
}
else {
resolvedAddress = linkedLibraryAddress;
}
if (!ethers.isAddress(resolvedAddress)) {
throw new HardhatError(HardhatError.ERRORS.HARDHAT_ETHERS.GENERAL.INVALID_ADDRESS_TO_LINK_CONTRACT_TO_LIBRARY, {
contractName: artifact.contractName,
linkedLibraryName,
resolvedAddress,
});
}
const matchingNeededLibraries = neededLibraries.filter((lib) => {
return (lib.libName === linkedLibraryName ||
`${lib.sourceName}:${lib.libName}` === linkedLibraryName);
});
if (matchingNeededLibraries.length === 0) {
let detailedMessage;
if (neededLibraries.length > 0) {
const libraryFQNames = neededLibraries
.map((lib) => `${lib.sourceName}:${lib.libName}`)
.map((x) => `* ${x}`)
.join("\n");
detailedMessage = `The libraries needed are:
${libraryFQNames}`;
}
else {
detailedMessage = "This contract doesn't need linking any libraries.";
}
throw new HardhatError(HardhatError.ERRORS.HARDHAT_ETHERS.GENERAL.LIBRARY_NOT_AMONG_CONTRACT_LIBRARIES, {
contractName: artifact.contractName,
linkedLibraryName,
detailedMessage,
});
}
if (matchingNeededLibraries.length > 1) {
const matchingNeededLibrariesFQNs = matchingNeededLibraries
.map(({ sourceName, libName }) => `${sourceName}:${libName}`)
.map((x) => `* ${x}`)
.join("\n");
throw new HardhatError(HardhatError.ERRORS.HARDHAT_ETHERS.GENERAL.AMBIGUOUS_LIBRARY_NAME, {
contractName: artifact.contractName,
linkedLibraryName,
matchingNeededLibrariesFQNs,
});
}
const [neededLibrary] = matchingNeededLibraries;
const neededLibraryFQN = `${neededLibrary.sourceName}:${neededLibrary.libName}`;
// The only way for this library to be already mapped is
// for it to be given twice in the libraries user input:
// once as a library name and another as a fully qualified library name.
if (linksToApply.has(neededLibraryFQN)) {
throw new HardhatError(HardhatError.ERRORS.HARDHAT_ETHERS.GENERAL.REFERENCE_TO_SAME_LIBRARY, {
linkedLibraryName1: neededLibrary.libName,
linkedLibraryName2: neededLibraryFQN,
});
}
linksToApply.set(neededLibraryFQN, {
sourceName: neededLibrary.sourceName,
libraryName: neededLibrary.libName,
address: resolvedAddress,
});
}
if (linksToApply.size < neededLibraries.length) {
const missingLibraries = neededLibraries
.map((lib) => `${lib.sourceName}:${lib.libName}`)
.filter((libFQName) => !linksToApply.has(libFQName))
.map((x) => `* ${x}`)
.join("\n");
throw new HardhatError(HardhatError.ERRORS.HARDHAT_ETHERS.GENERAL.MISSING_LINK_FOR_LIBRARY, {
contractName: artifact.contractName,
missingLibraries,
});
}
return this.#linkBytecode(artifact, [...linksToApply.values()]);
}
#linkBytecode(artifact, libraries) {
let bytecode = artifact.bytecode;
// TODO: measure performance impact
for (const { sourceName, libraryName, address } of libraries) {
const linkReferences = artifact.linkReferences[sourceName][libraryName];
for (const { start, length } of linkReferences) {
bytecode =
bytecode.substr(0, 2 + start * 2) +
address.substr(2) +
bytecode.substr(2 + (start + length) * 2);
}
}
return bytecode;
}
async #getContractFactoryByAbiAndBytecode(abi, bytecode, signer) {
const { ContractFactory } = await import("ethers");
if (signer === undefined) {
// const signers = await hre.ethers.getSigners();
const signers = await this.getSigners();
signer = signers[0];
}
return new ContractFactory(abi, bytecode, signer);
}
}
//# sourceMappingURL=hardhat-helpers.js.map