diff --git a/.changeset/small-buckets-ring.md b/.changeset/small-buckets-ring.md new file mode 100644 index 00000000000..8d9bc37139c --- /dev/null +++ b/.changeset/small-buckets-ring.md @@ -0,0 +1,5 @@ +--- +"@nomicfoundation/hardhat-typechain": patch +--- + +Added support for the `attach` method in `hardhat-typechain`. diff --git a/v-next/hardhat-typechain/src/internal/generate-types.ts b/v-next/hardhat-typechain/src/internal/generate-types.ts index 59b66d6ff63..1ffb2e85100 100644 --- a/v-next/hardhat-typechain/src/internal/generate-types.ts +++ b/v-next/hardhat-typechain/src/internal/generate-types.ts @@ -110,6 +110,8 @@ function addCompiledFilesTransformerIfAbsent( 'declare module "@nomicfoundation/hardhat-ethers/types"', ); + modifiedContent = addSupportForAttachMethod(modifiedContent); + return modifiedContent; }; @@ -140,3 +142,45 @@ export function addJsExtensionsIfNeeded(content: string): string { `import ${imports} from ${quote}${path}/index.js${quote};`, ); } + +// We expect the structure of the factory files to be: +// /* eslint-disable */ +// ... +// export class [contractName]__factory extends ContractFactory { +// ... +// static connect( +// ... +// } +function addSupportForAttachMethod(modifiedContent: string): string { + const pattern = /class\s+(\w+)__factory/; // Pattern to find the contract name in factory files + const match = modifiedContent.match(pattern); + + if (match === null) { + // File is not a factory file, so there is no need to modify it + return modifiedContent; + } + + const contractName = match[1]; + + // Insert the "attach" snippet right before the "connect" method + const insertPoint = modifiedContent.lastIndexOf("static connect("); + + const attachMethod = ` + override attach(address: string | Addressable): ${contractName} { + return super.attach(address) as ${contractName}; + } + `; + + modifiedContent = + modifiedContent.slice(0, insertPoint) + + attachMethod + + modifiedContent.slice(insertPoint); + + // Import the "Addressable" type as it is required by the "attach" method + modifiedContent = modifiedContent.replace( + "/* eslint-disable */", + '/* eslint-disable */\nimport type { Addressable } from "ethers";', + ); + + return modifiedContent; +} diff --git a/v-next/hardhat-typechain/test/index.ts b/v-next/hardhat-typechain/test/index.ts index 25cfed3e7dd..6fe4800f912 100644 --- a/v-next/hardhat-typechain/test/index.ts +++ b/v-next/hardhat-typechain/test/index.ts @@ -18,11 +18,6 @@ describe("hardhat-typechain", () => { before(async () => { await remove(`${process.cwd()}/types`); - }); - - it("should generate the types", async () => { - // Check that the types are generated with the expected addition of the "/index.js" extensions - // and the v3 modules const hardhatConfig = await import( // eslint-disable-next-line import/no-relative-packages -- allow for fixture projects @@ -36,6 +31,11 @@ describe("hardhat-typechain", () => { await hre.tasks.getTask("clean").run(); await hre.tasks.getTask("compile").run(); + }); + + it("should generate the types for the `hardhat.d.ts` file", async () => { + // Check that the types are generated with the expected addition of the "/index.js" extensions + // and the v3 modules const content = await readUtf8File( path.join(process.cwd(), "types", "ethers-contracts", "hardhat.d.ts"), @@ -63,12 +63,28 @@ describe("hardhat-typechain", () => { // The import from a npm package should have ".js" extensions assert.equal(content.includes(`import { ethers } from 'ethers'`), true); + }); - // Check that the types for the contract are generated - assert.equal( - await exists( - `${process.cwd()}/types/ethers-contracts/factories/A__factory.ts`, + it("should generated types for the contracts and add the support for the `attach` method", async () => { + const content = await readUtf8File( + path.join( + process.cwd(), + "types", + "ethers-contracts", + "factories", + "A__factory.ts", ), + ); + + // The "Addressable" type should be imported + assert.equal( + content.includes(`import type { Addressable } from "ethers";`), + true, + ); + + // The "attach" method should be added to the factory + assert.equal( + content.includes(`override attach(address: string | Addressable): A {`), true, ); });