Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4a1208f
draft solution
ChristopherDedominici Apr 2, 2026
58c6d15
use error code 3 from `SolidityError`
ChristopherDedominici Apr 7, 2026
693af7b
align viem-assetions packages
ChristopherDedominici Apr 7, 2026
38469a8
Merge branch 'main' of github.com:NomicFoundation/hardhat into error-…
ChristopherDedominici Apr 7, 2026
e27a7ad
Create cool-radios-type.md
ChristopherDedominici Apr 7, 2026
fdbe457
Merge branch 'error-code-3' of github.com:NomicFoundation/hardhat int…
ChristopherDedominici Apr 7, 2026
6f69bf0
clean code and add tests
ChristopherDedominici Apr 7, 2026
e8a096b
lint fix
ChristopherDedominici Apr 7, 2026
c2b356e
update viem version
alcuadrado Apr 8, 2026
2f315c7
Merge branch 'error-code-3' of github.com:NomicFoundation/hardhat int…
ChristopherDedominici Apr 9, 2026
604a1e1
add tests to ensure the correct errors are propagated
ChristopherDedominici Apr 9, 2026
fa00124
Merge branch 'main' of github.com:NomicFoundation/hardhat into error-…
ChristopherDedominici Apr 9, 2026
aee237a
changeset + peer bump
ChristopherDedominici Apr 9, 2026
4a78a6d
revert old logic for `data`
ChristopherDedominici Apr 9, 2026
ccd22ea
undo tests after removing data format
ChristopherDedominici Apr 9, 2026
ebb6b1d
implement copilot comments
ChristopherDedominici Apr 9, 2026
6e54860
extract revert code
ChristopherDedominici Apr 9, 2026
b854121
add check on `raw` filed for viem + use `useEphemeralFixtureProject` …
ChristopherDedominici Apr 10, 2026
b5e2f69
revert src code and test
ChristopherDedominici Apr 13, 2026
5cb67ab
move test in viem package
ChristopherDedominici Apr 13, 2026
752401d
Merge branch 'main' of github.com:NomicFoundation/hardhat into error-…
ChristopherDedominici Apr 13, 2026
92ba0a7
Update packages/hardhat-viem/test/revert-error-chain.ts
ChristopherDedominici Apr 14, 2026
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
11 changes: 11 additions & 0 deletions .changeset/cool-radios-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@nomicfoundation/hardhat-ethers": patch
"@nomicfoundation/hardhat-ignition-viem": patch
"@nomicfoundation/hardhat-ignition": patch
"@nomicfoundation/hardhat-toolbox-viem": patch
"@nomicfoundation/hardhat-viem-assertions": patch
"@nomicfoundation/hardhat-viem": patch
"hardhat": patch
---

Use code 3 for JSON-RPC revert error codes to align with standard node behavior and preserve error causes in viem/ethers.
2 changes: 1 addition & 1 deletion packages/example-project/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@
"prettier": "3.2.5",
"rimraf": "^5.0.5",
"typescript": "~5.8.0",
"viem": "^2.43.0"
"viem": "^2.47.6"
}
}
96 changes: 96 additions & 0 deletions packages/hardhat-ethers/test/revert-error-cause.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import type { HardhatEthers } from "../src/types.js";
import type { JsonRpcServer } from "hardhat/types/network";

import assert from "node:assert/strict";
import { after, before, beforeEach, describe, it } from "node:test";

import { ensureError } from "@nomicfoundation/hardhat-utils/error";

import { initializeTestEthers, spawnTestRpcServer } from "./helpers/helpers.js";

let ethers: HardhatEthers;

describe("revert error cause chain", () => {
describe("in-process EDR network", async () => {
beforeEach(async () => {
({ ethers } = await initializeTestEthers([
{ artifactName: "Contract", fileName: "error-messages" },
]));
});

it("should have SolidityError as the thrown error", async () => {
const contract = await ethers.deployContract("Contract");

let caughtError: Error | undefined;
try {
await contract.revertsWithReasonString();
} catch (error) {
ensureError(error);
caughtError = error;
}

assert.ok(
caughtError !== undefined && caughtError.name === "SolidityError",
`Expected SolidityError, got ${caughtError?.name}`,
);
assert.ok(
"code" in caughtError && caughtError.code === 3,
`Expected error code 3, got ${"code" in caughtError ? String(caughtError.code) : "undefined"}`,
);
assert.ok(
caughtError.message.includes("some reason"),
`Expected message to include "some reason", got: "${caughtError.message}"`,
);
});
});

describe("hardhat node (HTTP)", async () => {
let server: JsonRpcServer;
let port: number;
let address: string;

before(async () => {
({ server, port, address } = await spawnTestRpcServer());
});

after(async () => {
await server.close();
});

beforeEach(async () => {
({ ethers } = await initializeTestEthers(
[{ artifactName: "Contract", fileName: "error-messages" }],
{
networks: {
localhost: { type: "http", url: `http://${address}:${port}` },
},
},
));
});

it("should have ProviderError as the thrown error", async () => {
const contract = await ethers.deployContract("Contract");

let caughtError: Error | undefined;
try {
await contract.revertsWithReasonString();
} catch (error) {
ensureError(error);
caughtError = error;
}

assert.ok(
caughtError !== undefined && caughtError.name === "ProviderError",
`Expected ProviderError, got ${caughtError?.name}`,
);
assert.ok(
"code" in caughtError && caughtError.code === 3,
`Expected error code 3, got ${"code" in caughtError ? String(caughtError.code) : "undefined"}`,
);
assert.ok(
caughtError.message.includes("some reason"),
`Expected message to include "some reason", got: "${caughtError.message}"`,
);
});
});
});
4 changes: 2 additions & 2 deletions packages/hardhat-ignition-viem/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"ts-node": "10.9.2",
"tsx": "^4.19.3",
"typescript": "~5.8.0",
"viem": "^2.43.0",
"viem": "^2.47.6",
"@nomicfoundation/hardhat-verify": "workspace:^3.0.0"
},
"dependencies": {
Expand All @@ -76,6 +76,6 @@
"@nomicfoundation/ignition-core": "workspace:^3.0.7",
"@nomicfoundation/hardhat-verify": "workspace:^3.0.0",
"@nomicfoundation/hardhat-viem": "workspace:^3.0.4",
"viem": "^2.43.0"
"viem": "^2.47.6"
}
}
2 changes: 1 addition & 1 deletion packages/hardhat-ignition/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"sinon": "^14.0.0",
"ts-node": "10.9.2",
"typescript": "~5.8.0",
"viem": "^2.43.0",
"viem": "^2.47.6",
"@nomicfoundation/hardhat-verify": "workspace:^3.0.0"
},
"dependencies": {
Expand Down
4 changes: 2 additions & 2 deletions packages/hardhat-toolbox-viem/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"rimraf": "^5.0.5",
"tsx": "^4.19.3",
"typescript": "~5.8.0",
"viem": "^2.43.0",
"viem": "^2.47.6",
"hardhat": "workspace:^3.0.0"
},
"peerDependencies": {
Expand All @@ -77,6 +77,6 @@
"@nomicfoundation/hardhat-verify": "workspace:^3.0.0",
"@nomicfoundation/ignition-core": "workspace:^3.0.7",
"hardhat": "workspace:^3.0.0",
"viem": "^2.43.0"
"viem": "^2.47.6"
}
}
4 changes: 2 additions & 2 deletions packages/hardhat-viem-assertions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"rimraf": "^5.0.5",
"tsx": "^4.19.3",
"typescript": "~5.8.0",
"viem": "^2.43.0",
"viem": "^2.47.6",
"hardhat": "workspace:^3.0.0"
},
"dependencies": {
Expand All @@ -68,6 +68,6 @@
"peerDependencies": {
"@nomicfoundation/hardhat-viem": "workspace:^3.0.4",
"hardhat": "workspace:^3.0.0",
"viem": "^2.43.0"
"viem": "^2.47.6"
}
}
4 changes: 2 additions & 2 deletions packages/hardhat-viem/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"rimraf": "^5.0.5",
"tsx": "^4.19.3",
"typescript": "~5.8.0",
"viem": "^2.43.0",
"viem": "^2.47.6",
"hardhat": "workspace:^3.1.11"
},
"dependencies": {
Expand All @@ -64,6 +64,6 @@
},
"peerDependencies": {
"hardhat": "workspace:^3.1.11",
"viem": "^2.43.0"
"viem": "^2.47.6"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.0;

contract Revert {
function alwaysRevert() external pure {
revert("Intentional revert for testing purposes");
}
}
173 changes: 173 additions & 0 deletions packages/hardhat-viem/test/revert-error-chain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import type { HardhatViemHelpers } from "@nomicfoundation/hardhat-viem/types";

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.

I think this tests should be present in hardhat-viem, not this package.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done here: 5cb67ab

import type { HardhatRuntimeEnvironment } from "hardhat/types/hre";
import type { JsonRpcServer } from "hardhat/types/network";

import assert from "node:assert/strict";
import { after, before, beforeEach, describe, it } from "node:test";

import { useEphemeralFixtureProject } from "@nomicfoundation/hardhat-test-utils";
import { ensureError } from "@nomicfoundation/hardhat-utils/error";
import { createHardhatRuntimeEnvironment } from "hardhat/hre";
import {
ContractFunctionExecutionError,
ContractFunctionRevertedError,
} from "viem";

import hardhatViem from "../src/index.js";

describe("revert error cause chain", () => {
useEphemeralFixtureProject("default-ts-project");

describe("in-process hardhat network", () => {
let hre: HardhatRuntimeEnvironment;
let viem: HardhatViemHelpers;

before(async () => {
hre = await createHardhatRuntimeEnvironment({
plugins: [hardhatViem],
});

await hre.tasks.getTask("build").run({});
});

beforeEach(async () => {
({ viem } = await hre.network.connect());
});

it("should have ContractFunctionExecutionError as the thrown error", async () => {
const contract = await viem.deployContract("Revert");

let caughtError: Error | undefined;
try {
await contract.read.alwaysRevert();
} catch (error) {
ensureError(error);
caughtError = error;
}

assert.ok(
caughtError !== undefined &&
caughtError instanceof ContractFunctionExecutionError,
`Expected ContractFunctionExecutionError, got ${caughtError?.name}`,
);
assert.ok(
caughtError.message.includes("Intentional revert for testing purposes"),
`Expected message to include "Intentional revert for testing purposes", got: "${caughtError.message}"`,
);
});

it("should have SolidityError in cause chain", async () => {
const contract = await viem.deployContract("Revert");

let caughtError: Error | undefined;
try {
await contract.read.alwaysRevert();
} catch (error) {
ensureError(error);
caughtError = error;
}

let current: Error | undefined = caughtError;
let solidityError: Error | undefined;

while (current !== undefined) {
if (current.name === "SolidityError") {
solidityError = current;
break;
}

current = current.cause instanceof Error ? current.cause : undefined;
}

assert.ok(
solidityError !== undefined,
"Expected to find SolidityError in the cause chain",
);
assert.ok(
"code" in solidityError && solidityError.code === 3,
`Expected error code 3, got ${"code" in solidityError ? String(solidityError.code) : "undefined"}`,
);
});
});

describe("hardhat node (HTTP)", () => {
let hre: HardhatRuntimeEnvironment;
let viem: HardhatViemHelpers;
let server: JsonRpcServer;
let address: string;
let port: number;

before(async () => {
Comment thread
ChristopherDedominici marked this conversation as resolved.
hre = await createHardhatRuntimeEnvironment({
plugins: [hardhatViem],
networks: {
localhost: { type: "http", url: "http://127.0.0.1:0" },
},
});

await hre.tasks.getTask("build").run({});

server = await hre.network.createServer();
({ address, port } = await server.listen());
});

after(async () => {
await server.close();
});

beforeEach(async () => {
({ viem } = await hre.network.connect({
network: "localhost",
override: {
url: `http://${address}:${port}`,
},
Comment on lines +110 to +123

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

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

In the HTTP suite, the test uses the address returned by server.listen() to build the client URL. When the JSON-RPC server is bound to 0.0.0.0/:: (common in Docker/CI), that address is not always a reliable connect target for HTTP clients and can make this test flaky. Prefer binding the server explicitly to 127.0.0.1 (e.g. via hre.network.createServer("default", "127.0.0.1")) and using 127.0.0.1 in the override URL, while still keeping port from listen().

Copilot uses AI. Check for mistakes.
}));
});

it("should have ContractFunctionExecutionError as the thrown error", async () => {
const contract = await viem.deployContract("Revert");

let caughtError: Error | undefined;
try {
await contract.read.alwaysRevert();
} catch (error) {
ensureError(error);
caughtError = error;
}

assert.ok(
caughtError !== undefined &&
caughtError instanceof ContractFunctionExecutionError,
`Expected ContractFunctionExecutionError, got ${caughtError?.name}`,
);
assert.ok(
caughtError.message.includes("Intentional revert for testing purposes"),
`Expected message to include "Intentional revert for testing purposes", got: "${caughtError.message}"`,
);

let current: Error | undefined = caughtError;
let errorWithCode3: Error | undefined;

while (current !== undefined) {
if ("code" in current && current.code === 3) {
errorWithCode3 = current;
break;
}

current = current.cause instanceof Error ? current.cause : undefined;
}

assert.ok(
errorWithCode3 !== undefined,
"Expected to find an error with code 3 in the cause chain",
);

const cause = caughtError.cause;
ensureError(cause, ContractFunctionRevertedError);
assert.ok(
"raw" in cause && cause.raw !== undefined,
"Expected raw revert data to be present on ContractFunctionRevertedError",
);
});
Comment thread
ChristopherDedominici marked this conversation as resolved.
});
});
Loading
Loading