Skip to content

Commit bc3bd6a

Browse files
committed
Prevent losing typechain types when compiling a subset of contracts
1 parent db98630 commit bc3bd6a

File tree

4 files changed

+105
-16
lines changed

4 files changed

+105
-16
lines changed

.changeset/sour-mayflies-speak.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@nomicfoundation/hardhat-typechain": patch
3+
---
4+
5+
Fix typechain overwrite when compiling a subset of contracts

v-next/hardhat-typechain/src/internal/hook-handlers/solidity.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,20 @@ export default async (): Promise<Partial<SolidityHooks>> => {
1313
artifacts: Map<CompilationJob, ReadonlyMap<string, string[]>>,
1414
) => Promise<void>,
1515
) {
16-
const artifactsPaths = Array.from(artifacts.values()).flatMap(
16+
const currentArtifactsPaths = Array.from(artifacts.values()).flatMap(
1717
(innerMap) => Array.from(innerMap.values()).flat(),
1818
);
1919

20+
const existingArtifactsPaths = await Promise.all(
21+
Array.from(await context.artifacts.getAllFullyQualifiedNames()).map(
22+
(name) => context.artifacts.getArtifactPath(name),
23+
),
24+
);
25+
26+
const artifactsPaths = Array.from(
27+
new Set([...currentArtifactsPaths, ...existingArtifactsPaths]),
28+
);
29+
2030
await generateTypes(
2131
context.config.paths.root,
2232
context.config.typechain,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
contract B {
5+
function getMessage() external pure returns (string memory) {
6+
return "Hello from B contract!";
7+
}
8+
}

v-next/hardhat-typechain/test/index.ts

+81-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import type { HardhatRuntimeEnvironment } from "hardhat/types/hre";
2+
13
import assert from "node:assert/strict";
24
import path from "node:path";
3-
import { before, describe, it } from "node:test";
5+
import { before, beforeEach, describe, it } from "node:test";
46

57
import { useFixtureProject } from "@nomicfoundation/hardhat-test-utils";
68
import {
@@ -10,30 +12,61 @@ import {
1012
} from "@nomicfoundation/hardhat-utils/fs";
1113
import { createHardhatRuntimeEnvironment } from "hardhat/hre";
1214

15+
// Read the contract factory from the generated types
16+
async function readContractFactory(contractName: string) {
17+
const potentialPaths = [
18+
`${process.cwd()}/types/ethers-contracts/factories/${contractName}__factory.ts`,
19+
`${process.cwd()}/types/ethers-contracts/factories/${contractName}.sol/${contractName}__factory.ts`,
20+
];
21+
for (const potentialPath of potentialPaths) {
22+
if (await exists(potentialPath)) {
23+
return readUtf8File(potentialPath);
24+
}
25+
}
26+
return undefined;
27+
}
28+
29+
// Check the contract is typed in hardhat.d.ts
30+
function isContractTyped(typeFileContents: string, contractName: string) {
31+
const lookupStrings = [
32+
`getContractFactory(name: '${contractName}'`,
33+
`getContractAt(name: '${contractName}'`,
34+
`deployContract(name: '${contractName}'`,
35+
];
36+
for (const lookupString of lookupStrings) {
37+
if (!typeFileContents.includes(lookupString)) {
38+
return false;
39+
}
40+
}
41+
return true;
42+
}
43+
1344
describe("hardhat-typechain", () => {
1445
describe("check that types are generated correctly", () => {
1546
const projectFolder = "generate-types";
47+
let hre: HardhatRuntimeEnvironment;
48+
let hardhatConfig: any;
1649

1750
useFixtureProject(projectFolder);
1851

19-
before(async () => {
52+
beforeEach(async () => {
2053
await remove(`${process.cwd()}/types`);
2154

22-
const hardhatConfig = await import(
55+
hardhatConfig = await import(
2356
// eslint-disable-next-line import/no-relative-packages -- allow for fixture projects
2457
`./fixture-projects/${projectFolder}/hardhat.config.js`
2558
);
2659

27-
const hre = await createHardhatRuntimeEnvironment(hardhatConfig.default);
60+
hre = await createHardhatRuntimeEnvironment(hardhatConfig.default);
2861

2962
assert.equal(await exists(`${process.cwd()}/types`), false);
3063

3164
await hre.tasks.getTask("clean").run();
32-
33-
await hre.tasks.getTask("compile").run();
3465
});
3566

3667
it("should generate the types for the `hardhat.d.ts` file", async () => {
68+
await hre.tasks.getTask("compile").run();
69+
3770
// Check that the types are generated with the expected addition of the "/index.js" extensions
3871
// and the v3 modules
3972

@@ -63,18 +96,24 @@ describe("hardhat-typechain", () => {
6396

6497
// The import from a npm package should have ".js" extensions
6598
assert.equal(content.includes(`import { ethers } from 'ethers'`), true);
99+
100+
for (const contractName of ["A", "B"]) {
101+
assert.ok(
102+
(await readContractFactory(contractName)) !== undefined,
103+
`Factory for ${contractName} doesnt exist`,
104+
);
105+
assert.equal(isContractTyped(content, contractName), true);
106+
}
66107
});
67108

68109
it("should generated types for the contracts and add the support for the `attach` method", async () => {
69-
const content = await readUtf8File(
70-
path.join(
71-
process.cwd(),
72-
"types",
73-
"ethers-contracts",
74-
"factories",
75-
"A__factory.ts",
76-
),
77-
);
110+
await hre.tasks.getTask("compile").run();
111+
112+
const content = await readContractFactory("A");
113+
114+
if (content === undefined) {
115+
throw new Error("Factory for A.sol not found");
116+
}
78117

79118
// The "Addressable" type should be imported
80119
assert.equal(
@@ -88,6 +127,33 @@ describe("hardhat-typechain", () => {
88127
true,
89128
);
90129
});
130+
131+
it("doesnt lose types when compiling a subset of the contracts", async () => {
132+
// First: compile only A.sol. Only A should be typed
133+
await hre.tasks.getTask("compile").run({ files: ["contracts/A.sol"] });
134+
135+
assert.notEqual(await readContractFactory("A"), undefined);
136+
assert.equal(await readContractFactory("B"), undefined);
137+
138+
let content = await readUtf8File(
139+
path.join(process.cwd(), "types", "ethers-contracts", "hardhat.d.ts"),
140+
);
141+
142+
assert.equal(isContractTyped(content, "A"), true);
143+
assert.equal(isContractTyped(content, "B"), false);
144+
145+
// Second: compile only B.sol. Both A and B should be typed
146+
await hre.tasks.getTask("compile").run({ files: ["contracts/B.sol"] });
147+
148+
assert.notEqual(await readContractFactory("A"), undefined);
149+
assert.notEqual(await readContractFactory("B"), undefined);
150+
151+
content = await readUtf8File(
152+
path.join(process.cwd(), "types", "ethers-contracts", "hardhat.d.ts"),
153+
);
154+
assert.equal(isContractTyped(content, "A"), true);
155+
assert.equal(isContractTyped(content, "B"), true);
156+
});
91157
});
92158

93159
describe("typechain should not generate types during compilation", () => {

0 commit comments

Comments
 (0)