diff --git a/.changeset/twelve-rabbits-draw.md b/.changeset/twelve-rabbits-draw.md new file mode 100644 index 00000000000..bfa33f39000 --- /dev/null +++ b/.changeset/twelve-rabbits-draw.md @@ -0,0 +1,5 @@ +--- +"hardhat": patch +--- + +Optimize `hre.artifacts.artifactExists()` diff --git a/packages/hardhat/src/internal/builtin-plugins/artifacts/artifact-manager.ts b/packages/hardhat/src/internal/builtin-plugins/artifacts/artifact-manager.ts index c13f5d46475..4000802ac0a 100644 --- a/packages/hardhat/src/internal/builtin-plugins/artifacts/artifact-manager.ts +++ b/packages/hardhat/src/internal/builtin-plugins/artifacts/artifact-manager.ts @@ -81,22 +81,16 @@ export class ArtifactManagerImplementation implements ArtifactManager { public async artifactExists( contractNameOrFullyQualifiedName: string, ): Promise { - try { - // This throw if the artifact doesn't exist - await this.getArtifactPath(contractNameOrFullyQualifiedName); - - return true; - } catch (error) { - if (HardhatError.isHardhatError(error)) { - if ( - error.number === HardhatError.ERRORS.CORE.ARTIFACTS.NOT_FOUND.number - ) { - return false; - } - } + const { bareNameToFullyQualifiedNameMap, allFullyQualifiedNames } = + await this.#getFsData(); - throw error; + if (this.#isFullyQualifiedName(contractNameOrFullyQualifiedName)) { + return allFullyQualifiedNames.has(contractNameOrFullyQualifiedName); } + + return bareNameToFullyQualifiedNameMap.has( + contractNameOrFullyQualifiedName, + ); } public async getBuildInfoId( diff --git a/packages/hardhat/src/types/artifacts.ts b/packages/hardhat/src/types/artifacts.ts index a569bfa96b7..88c966d3ba5 100644 --- a/packages/hardhat/src/types/artifacts.ts +++ b/packages/hardhat/src/types/artifacts.ts @@ -89,11 +89,11 @@ export interface ArtifactManager { /** * Returns true if an artifact exists. * - * This function doesn't throw if the name is not unique. + * This function doesn't throw if the name is not unique, this means that + * this method may return `true`, and a successive call to `readArtifact` can + * throw because of a duplicated name. * * @param contractNameOrFullyQualifiedName Contract or fully qualified name. - * @throws Throws an error if a non-unique contract name is used, - * indicating which fully qualified names can be used instead. */ artifactExists(contractNameOrFullyQualifiedName: string): Promise; @@ -120,7 +120,8 @@ export interface ArtifactManager { * If it does return an id, it's not guaranteed that the build info is * present. * - * @param contractNameOrFullyQualifiedName Contract or fully qualified name, whose artifact must exist. + * @param contractNameOrFullyQualifiedName Contract or fully qualified name, + * whose artifact must exist. * @throws Throws an error if a non-unique contract name is used, * indicating which fully qualified names can be used instead. * @throws Throws an error if the artifact doesn't exist. diff --git a/packages/hardhat/test/fixture-projects/artifacts/duplicate-names-project/.gitignore b/packages/hardhat/test/fixture-projects/artifacts/duplicate-names-project/.gitignore new file mode 100644 index 00000000000..e7f801166c5 --- /dev/null +++ b/packages/hardhat/test/fixture-projects/artifacts/duplicate-names-project/.gitignore @@ -0,0 +1,2 @@ +artifacts/ +cache/ diff --git a/packages/hardhat/test/fixture-projects/artifacts/duplicate-names-project/contracts/a/Foo.sol b/packages/hardhat/test/fixture-projects/artifacts/duplicate-names-project/contracts/a/Foo.sol new file mode 100644 index 00000000000..d4a18f04b6e --- /dev/null +++ b/packages/hardhat/test/fixture-projects/artifacts/duplicate-names-project/contracts/a/Foo.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract Foo { + uint public x; +} diff --git a/packages/hardhat/test/fixture-projects/artifacts/duplicate-names-project/contracts/b/Foo.sol b/packages/hardhat/test/fixture-projects/artifacts/duplicate-names-project/contracts/b/Foo.sol new file mode 100644 index 00000000000..d89bca11bbc --- /dev/null +++ b/packages/hardhat/test/fixture-projects/artifacts/duplicate-names-project/contracts/b/Foo.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract Foo { + uint public y; +} diff --git a/packages/hardhat/test/fixture-projects/artifacts/duplicate-names-project/hardhat.config.ts b/packages/hardhat/test/fixture-projects/artifacts/duplicate-names-project/hardhat.config.ts new file mode 100644 index 00000000000..ff8b4c56321 --- /dev/null +++ b/packages/hardhat/test/fixture-projects/artifacts/duplicate-names-project/hardhat.config.ts @@ -0,0 +1 @@ +export default {}; diff --git a/packages/hardhat/test/fixture-projects/artifacts/duplicate-names-project/package.json b/packages/hardhat/test/fixture-projects/artifacts/duplicate-names-project/package.json new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/packages/hardhat/test/fixture-projects/artifacts/duplicate-names-project/package.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/hardhat/test/internal/builtin-plugins/artifacts/artifact-manager.ts b/packages/hardhat/test/internal/builtin-plugins/artifacts/artifact-manager.ts index ea865630c94..aac877dbaf5 100644 --- a/packages/hardhat/test/internal/builtin-plugins/artifacts/artifact-manager.ts +++ b/packages/hardhat/test/internal/builtin-plugins/artifacts/artifact-manager.ts @@ -105,4 +105,69 @@ describe("ArtifactManagerImplementation", () => { ); }); }); + + describe("artifactExists", () => { + it("should return true for an existing bare name", async () => { + const result = await artifactManager.artifactExists("Counter"); + assert.equal(result, true); + }); + + it("should return true for an existing fully-qualified name", async () => { + const result = await artifactManager.artifactExists( + "contracts/Counter.sol:Counter", + ); + assert.equal(result, true); + }); + + it("should return false for a missing bare name", async () => { + const result = await artifactManager.artifactExists("NonExistent"); + assert.equal(result, false); + }); + + it("should return false for a missing fully-qualified name", async () => { + const result = await artifactManager.artifactExists( + "contracts/NonExistent.sol:NonExistent", + ); + assert.equal(result, false); + + const result2 = await artifactManager.artifactExists( + "contracts/Counter.sol:NonExistent", + ); + assert.equal(result2, false); + }); + }); +}); + +describe("ArtifactManagerImplementation - duplicate bare names", () => { + useFixtureProject("artifacts/duplicate-names-project"); + + let hre: HardhatRuntimeEnvironment; + let artifactManager: ArtifactManagerImplementation; + before(async () => { + hre = await createHardhatRuntimeEnvironment({}); + + await hre.tasks.getTask(["build"]).run({}); + + artifactManager = new ArtifactManagerImplementation( + hre.config.paths.artifacts, + ); + }); + + describe("artifactExists", () => { + it("should return true and not throw when multiple artifacts share the same bare name", async () => { + const result = await artifactManager.artifactExists("Foo"); + assert.equal(result, true); + }); + + it("should return true for each fully-qualified name individually", async () => { + const resultA = await artifactManager.artifactExists( + "contracts/a/Foo.sol:Foo", + ); + const resultB = await artifactManager.artifactExists( + "contracts/b/Foo.sol:Foo", + ); + assert.equal(resultA, true); + assert.equal(resultB, true); + }); + }); });