Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Typechain] Investigate how to support the attach method #6442

Merged
merged 8 commits into from
Mar 7, 2025
5 changes: 5 additions & 0 deletions .changeset/small-buckets-ring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nomicfoundation/hardhat-typechain": patch
---

Added support for the `attach` method in `hardhat-typechain`.
44 changes: 44 additions & 0 deletions v-next/hardhat-typechain/src/internal/generate-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ function addCompiledFilesTransformerIfAbsent(
'declare module "@nomicfoundation/hardhat-ethers/types"',
);

modifiedContent = addSupportForAttachMethod(modifiedContent);

return modifiedContent;
};

Expand Down Expand Up @@ -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 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As with the other logic in this file, since Typechain is no longer maintained, we cannot open PRs to the repository, so we need to find a workaround.

The logic behind this code is:

  • Identify the files that extend ContractFactory
  • Modify these files to add support for the attach method
  • Add the required ethers import to support the attach method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this possibility in the design doc but discarded it due to several maintenance complications

const pattern = /class\s+(\w+)__factory/; // Pattern to find the contract name in factory files
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note for other reviewers because I checked it myself, \w+ is equivalent to [a-zA-Z0-9_]+. So it should cover all the characters allowed in contract names 👍

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;
}
34 changes: 25 additions & 9 deletions v-next/hardhat-typechain/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"),
Expand Down Expand Up @@ -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,
);
});
Expand Down
Loading