Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/twelve-rabbits-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"hardhat": patch
---

Optimize `hre.artifacts.artifactExists()`
Original file line number Diff line number Diff line change
Expand Up @@ -81,22 +81,16 @@ export class ArtifactManagerImplementation implements ArtifactManager {
public async artifactExists(
contractNameOrFullyQualifiedName: string,
): Promise<boolean> {
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,
);
}
Comment on lines 81 to 94

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

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

artifactExists behavior changed to return a boolean based on cached fs data (and no longer relies on getArtifactPath throwing). There are existing tests for ArtifactManagerImplementation in packages/hardhat/test/internal/builtin-plugins/artifacts/artifact-manager.ts, but none cover artifactExists—especially the non-unique bare-name case that this change is meant to handle. Add tests that assert: (1) returns true for an existing bare name, (2) returns true for an existing fully-qualified name, (3) returns false for a missing name, and (4) returns true (and does not throw) when multiple artifacts share the same bare name.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

good point, done


public async getBuildInfoId(
Expand Down
6 changes: 3 additions & 3 deletions packages/hardhat/src/types/artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 succesive call to `readArtifact` can
* throw because of a duplicated name.
Comment thread
alcuadrado marked this conversation as resolved.
Outdated
*
* @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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Given that this function’s semantics have changed, it would be good to call it out here. When there are multiple contracts with the same name, artifactExists("Foo") returning true no longer guarantees that readArtifact("Foo") will succeed:

if (await hre.artifacts.artifactExists("Foo")) {
  const a = await hre.artifacts.readArtifact("Foo"); // MULTIPLE_FOUND error
}

This could be confusing for users who expect readArtifact not to throw if artifactExists returned true.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'll add a comment. That's a good point.

Note that I don't think the intentional semantics changed. I did a bit of and this method didn't used to have the @throws. I made a mistake in this commit by adding every @throws possible, despite contradicting the description.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in b1e4fcb

*/
artifactExists(contractNameOrFullyQualifiedName: string): Promise<boolean>;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
artifacts/
cache/
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Foo {
uint public x;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Foo {
uint public y;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default {};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});
Loading