1 | import type { ethers } from "ethers";
|
2 | import type { SignerWithAddress } from "../signers";
|
3 | import type { FactoryOptions, Libraries } from "../types";
|
4 |
|
5 | import { NomicLabsHardhatPluginError } from "hardhat/plugins";
|
6 | import {
|
7 | Artifact,
|
8 | HardhatRuntimeEnvironment,
|
9 | NetworkConfig,
|
10 | } from "hardhat/types";
|
11 |
|
12 | interface Link {
|
13 | sourceName: string;
|
14 | libraryName: string;
|
15 | address: string;
|
16 | }
|
17 |
|
18 | const pluginName = "hardhat-ethers";
|
19 |
|
20 | function isArtifact(artifact: any): artifact is Artifact {
|
21 | const {
|
22 | contractName,
|
23 | sourceName,
|
24 | abi,
|
25 | bytecode,
|
26 | deployedBytecode,
|
27 | linkReferences,
|
28 | deployedLinkReferences,
|
29 | } = artifact;
|
30 |
|
31 | return (
|
32 | typeof contractName === "string" &&
|
33 | typeof sourceName === "string" &&
|
34 | Array.isArray(abi) &&
|
35 | typeof bytecode === "string" &&
|
36 | typeof deployedBytecode === "string" &&
|
37 | linkReferences !== undefined &&
|
38 | deployedLinkReferences !== undefined
|
39 | );
|
40 | }
|
41 |
|
42 | export async function getSigners(
|
43 | hre: HardhatRuntimeEnvironment
|
44 | ): Promise<SignerWithAddress[]> {
|
45 | const accounts = await hre.ethers.provider.listAccounts();
|
46 |
|
47 | const signersWithAddress = await Promise.all(
|
48 | accounts.map((account) => getSigner(hre, account))
|
49 | );
|
50 |
|
51 | return signersWithAddress;
|
52 | }
|
53 |
|
54 | export async function getSigner(
|
55 | hre: HardhatRuntimeEnvironment,
|
56 | address: string
|
57 | ): Promise<SignerWithAddress> {
|
58 | const { SignerWithAddress: SignerWithAddressImpl } = await import(
|
59 | "../signers"
|
60 | );
|
61 |
|
62 | const signer = hre.ethers.provider.getSigner(address);
|
63 |
|
64 | const signerWithAddress = await SignerWithAddressImpl.create(signer);
|
65 |
|
66 | return signerWithAddress;
|
67 | }
|
68 |
|
69 | export function getContractFactory(
|
70 | hre: HardhatRuntimeEnvironment,
|
71 | name: string,
|
72 | signerOrOptions?: ethers.Signer | FactoryOptions
|
73 | ): Promise<ethers.ContractFactory>;
|
74 |
|
75 | export function getContractFactory(
|
76 | hre: HardhatRuntimeEnvironment,
|
77 | abi: any[],
|
78 | bytecode: ethers.utils.BytesLike,
|
79 | signer?: ethers.Signer
|
80 | ): Promise<ethers.ContractFactory>;
|
81 |
|
82 | export async function getContractFactory(
|
83 | hre: HardhatRuntimeEnvironment,
|
84 | nameOrAbi: string | any[],
|
85 | bytecodeOrFactoryOptions?:
|
86 | | (ethers.Signer | FactoryOptions)
|
87 | | ethers.utils.BytesLike,
|
88 | signer?: ethers.Signer
|
89 | ) {
|
90 | if (typeof nameOrAbi === "string") {
|
91 | const artifact = await hre.artifacts.readArtifact(nameOrAbi);
|
92 |
|
93 | return getContractFactoryFromArtifact(
|
94 | hre,
|
95 | artifact,
|
96 | bytecodeOrFactoryOptions as ethers.Signer | FactoryOptions | undefined
|
97 | );
|
98 | }
|
99 |
|
100 | return getContractFactoryByAbiAndBytecode(
|
101 | hre,
|
102 | nameOrAbi,
|
103 | bytecodeOrFactoryOptions as ethers.utils.BytesLike,
|
104 | signer
|
105 | );
|
106 | }
|
107 |
|
108 | function isFactoryOptions(
|
109 | signerOrOptions?: ethers.Signer | FactoryOptions
|
110 | ): signerOrOptions is FactoryOptions {
|
111 | const { Signer } = require("ethers") as typeof ethers;
|
112 | if (signerOrOptions === undefined || signerOrOptions instanceof Signer) {
|
113 | return false;
|
114 | }
|
115 |
|
116 | return true;
|
117 | }
|
118 |
|
119 | export async function getContractFactoryFromArtifact(
|
120 | hre: HardhatRuntimeEnvironment,
|
121 | artifact: Artifact,
|
122 | signerOrOptions?: ethers.Signer | FactoryOptions
|
123 | ) {
|
124 | let libraries: Libraries = {};
|
125 | let signer: ethers.Signer | undefined;
|
126 |
|
127 | if (!isArtifact(artifact)) {
|
128 | throw new NomicLabsHardhatPluginError(
|
129 | pluginName,
|
130 | `You are trying to create a contract factory from an artifact, but you have not passed a valid artifact parameter.`
|
131 | );
|
132 | }
|
133 |
|
134 | if (isFactoryOptions(signerOrOptions)) {
|
135 | signer = signerOrOptions.signer;
|
136 | libraries = signerOrOptions.libraries ?? {};
|
137 | } else {
|
138 | signer = signerOrOptions;
|
139 | }
|
140 |
|
141 | if (artifact.bytecode === "0x") {
|
142 | throw new NomicLabsHardhatPluginError(
|
143 | pluginName,
|
144 | `You are trying to create a contract factory for the contract ${artifact.contractName}, which is abstract and can't be deployed.
|
145 | If you want to call a contract using ${artifact.contractName} as its interface use the "getContractAt" function instead.`
|
146 | );
|
147 | }
|
148 |
|
149 | const linkedBytecode = await collectLibrariesAndLink(artifact, libraries);
|
150 |
|
151 | return getContractFactoryByAbiAndBytecode(
|
152 | hre,
|
153 | artifact.abi,
|
154 | linkedBytecode,
|
155 | signer
|
156 | );
|
157 | }
|
158 |
|
159 | async function collectLibrariesAndLink(
|
160 | artifact: Artifact,
|
161 | libraries: Libraries
|
162 | ) {
|
163 | const { utils } = require("ethers") as typeof ethers;
|
164 |
|
165 | const neededLibraries: Array<{
|
166 | sourceName: string;
|
167 | libName: string;
|
168 | }> = [];
|
169 | for (const [sourceName, sourceLibraries] of Object.entries(
|
170 | artifact.linkReferences
|
171 | )) {
|
172 | for (const libName of Object.keys(sourceLibraries)) {
|
173 | neededLibraries.push({ sourceName, libName });
|
174 | }
|
175 | }
|
176 |
|
177 | const linksToApply: Map<string, Link> = new Map();
|
178 | for (const [linkedLibraryName, linkedLibraryAddress] of Object.entries(
|
179 | libraries
|
180 | )) {
|
181 | if (!utils.isAddress(linkedLibraryAddress)) {
|
182 | throw new NomicLabsHardhatPluginError(
|
183 | pluginName,
|
184 | `You tried to link the contract ${artifact.contractName} with the library ${linkedLibraryName}, but provided this invalid address: ${linkedLibraryAddress}`
|
185 | );
|
186 | }
|
187 |
|
188 | const matchingNeededLibraries = neededLibraries.filter((lib) => {
|
189 | return (
|
190 | lib.libName === linkedLibraryName ||
|
191 | `${lib.sourceName}:${lib.libName}` === linkedLibraryName
|
192 | );
|
193 | });
|
194 |
|
195 | if (matchingNeededLibraries.length === 0) {
|
196 | let detailedMessage: string;
|
197 | if (neededLibraries.length > 0) {
|
198 | const libraryFQNames = neededLibraries
|
199 | .map((lib) => `${lib.sourceName}:${lib.libName}`)
|
200 | .map((x) => `* ${x}`)
|
201 | .join("\n");
|
202 | detailedMessage = `The libraries needed are:
|
203 | ${libraryFQNames}`;
|
204 | } else {
|
205 | detailedMessage = "This contract doesn't need linking any libraries.";
|
206 | }
|
207 | throw new NomicLabsHardhatPluginError(
|
208 | pluginName,
|
209 | `You tried to link the contract ${artifact.contractName} with ${linkedLibraryName}, which is not one of its libraries.
|
210 | ${detailedMessage}`
|
211 | );
|
212 | }
|
213 |
|
214 | if (matchingNeededLibraries.length > 1) {
|
215 | const matchingNeededLibrariesFQNs = matchingNeededLibraries
|
216 | .map(({ sourceName, libName }) => `${sourceName}:${libName}`)
|
217 | .map((x) => `* ${x}`)
|
218 | .join("\n");
|
219 | throw new NomicLabsHardhatPluginError(
|
220 | pluginName,
|
221 | `The library name ${linkedLibraryName} is ambiguous for the contract ${artifact.contractName}.
|
222 | It may resolve to one of the following libraries:
|
223 | ${matchingNeededLibrariesFQNs}
|
224 |
|
225 | To fix this, choose one of these fully qualified library names and replace where appropriate.`
|
226 | );
|
227 | }
|
228 |
|
229 | const [neededLibrary] = matchingNeededLibraries;
|
230 |
|
231 | const neededLibraryFQN = `${neededLibrary.sourceName}:${neededLibrary.libName}`;
|
232 |
|
233 |
|
234 |
|
235 |
|
236 | if (linksToApply.has(neededLibraryFQN)) {
|
237 | throw new NomicLabsHardhatPluginError(
|
238 | pluginName,
|
239 | `The library names ${neededLibrary.libName} and ${neededLibraryFQN} refer to the same library and were given as two separate library links.
|
240 | Remove one of them and review your library links before proceeding.`
|
241 | );
|
242 | }
|
243 |
|
244 | linksToApply.set(neededLibraryFQN, {
|
245 | sourceName: neededLibrary.sourceName,
|
246 | libraryName: neededLibrary.libName,
|
247 | address: linkedLibraryAddress,
|
248 | });
|
249 | }
|
250 |
|
251 | if (linksToApply.size < neededLibraries.length) {
|
252 | const missingLibraries = neededLibraries
|
253 | .map((lib) => `${lib.sourceName}:${lib.libName}`)
|
254 | .filter((libFQName) => !linksToApply.has(libFQName))
|
255 | .map((x) => `* ${x}`)
|
256 | .join("\n");
|
257 |
|
258 | throw new NomicLabsHardhatPluginError(
|
259 | pluginName,
|
260 | `The contract ${artifact.contractName} is missing links for the following libraries:
|
261 | ${missingLibraries}
|
262 |
|
263 | Learn more about linking contracts at https://hardhat.org/plugins/nomiclabs-hardhat-ethers.html#library-linking
|
264 | `
|
265 | );
|
266 | }
|
267 |
|
268 | return linkBytecode(artifact, [...linksToApply.values()]);
|
269 | }
|
270 |
|
271 | async function getContractFactoryByAbiAndBytecode(
|
272 | hre: HardhatRuntimeEnvironment,
|
273 | abi: any[],
|
274 | bytecode: ethers.utils.BytesLike,
|
275 | signer?: ethers.Signer
|
276 | ) {
|
277 | const { ContractFactory } = require("ethers") as typeof ethers;
|
278 |
|
279 | if (signer === undefined) {
|
280 | const signers = await hre.ethers.getSigners();
|
281 | signer = signers[0];
|
282 | }
|
283 |
|
284 | const abiWithAddedGas = addGasToAbiMethodsIfNecessary(
|
285 | hre.network.config,
|
286 | abi
|
287 | );
|
288 |
|
289 | return new ContractFactory(abiWithAddedGas, bytecode, signer);
|
290 | }
|
291 |
|
292 | export async function getContractAt(
|
293 | hre: HardhatRuntimeEnvironment,
|
294 | nameOrAbi: string | any[],
|
295 | address: string,
|
296 | signer?: ethers.Signer
|
297 | ) {
|
298 | if (typeof nameOrAbi === "string") {
|
299 | const artifact = await hre.artifacts.readArtifact(nameOrAbi);
|
300 |
|
301 | return getContractAtFromArtifact(hre, artifact, address, signer);
|
302 | }
|
303 |
|
304 | const { Contract } = require("ethers") as typeof ethers;
|
305 |
|
306 | if (signer === undefined) {
|
307 | const signers = await hre.ethers.getSigners();
|
308 | signer = signers[0];
|
309 | }
|
310 |
|
311 |
|
312 |
|
313 | const signerOrProvider: ethers.Signer | ethers.providers.Provider =
|
314 | signer !== undefined ? signer : hre.ethers.provider;
|
315 |
|
316 | const abiWithAddedGas = addGasToAbiMethodsIfNecessary(
|
317 | hre.network.config,
|
318 | nameOrAbi
|
319 | );
|
320 |
|
321 | return new Contract(address, abiWithAddedGas, signerOrProvider);
|
322 | }
|
323 |
|
324 | export async function getContractAtFromArtifact(
|
325 | hre: HardhatRuntimeEnvironment,
|
326 | artifact: Artifact,
|
327 | address: string,
|
328 | signer?: ethers.Signer
|
329 | ) {
|
330 | if (!isArtifact(artifact)) {
|
331 | throw new NomicLabsHardhatPluginError(
|
332 | pluginName,
|
333 | `You are trying to create a contract by artifact, but you have not passed a valid artifact parameter.`
|
334 | );
|
335 | }
|
336 |
|
337 | const factory = await getContractFactoryByAbiAndBytecode(
|
338 | hre,
|
339 | artifact.abi,
|
340 | "0x",
|
341 | signer
|
342 | );
|
343 |
|
344 | let contract = factory.attach(address);
|
345 |
|
346 | if (contract.provider === null) {
|
347 | contract = contract.connect(hre.ethers.provider);
|
348 | }
|
349 |
|
350 | return contract;
|
351 | }
|
352 |
|
353 |
|
354 |
|
355 |
|
356 |
|
357 | function addGasToAbiMethodsIfNecessary(
|
358 | networkConfig: NetworkConfig,
|
359 | abi: any[]
|
360 | ): any[] {
|
361 | const { BigNumber } = require("ethers") as typeof ethers;
|
362 |
|
363 | if (networkConfig.gas === "auto" || networkConfig.gas === undefined) {
|
364 | return abi;
|
365 | }
|
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 |
|
372 | const gasLimit = BigNumber.from(networkConfig.gas).sub(1000000).toHexString();
|
373 |
|
374 | const modifiedAbi: any[] = [];
|
375 |
|
376 | for (const abiElement of abi) {
|
377 | if (abiElement.type !== "function") {
|
378 | modifiedAbi.push(abiElement);
|
379 | continue;
|
380 | }
|
381 |
|
382 | modifiedAbi.push({
|
383 | ...abiElement,
|
384 | gas: gasLimit,
|
385 | });
|
386 | }
|
387 |
|
388 | return modifiedAbi;
|
389 | }
|
390 |
|
391 | function linkBytecode(artifact: Artifact, libraries: Link[]): string {
|
392 | let bytecode = artifact.bytecode;
|
393 |
|
394 |
|
395 | for (const { sourceName, libraryName, address } of libraries) {
|
396 | const linkReferences = artifact.linkReferences[sourceName][libraryName];
|
397 | for (const { start, length } of linkReferences) {
|
398 | bytecode =
|
399 | bytecode.substr(0, 2 + start * 2) +
|
400 | address.substr(2) +
|
401 | bytecode.substr(2 + (start + length) * 2);
|
402 | }
|
403 | }
|
404 |
|
405 | return bytecode;
|
406 | }
|