Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 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.
5 changes: 5 additions & 0 deletions .peer-bumps.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
"package": "@nomicfoundation/hardhat-ignition-ethers",
"peer": "@nomicfoundation/hardhat-ignition",
"reason": "The usage of the new `getUserInterruptionsHandlers` helper from `@nomicfoundation/hardhat-ignition/helpers`"
},
{
"package": "@nomicfoundation/hardhat-viem-assertions",
"peer": "hardhat",
"reason": "The rewritten `extractRevertError` now relies on viem parsing a code-3 JSON-RPC error with raw hex data, which requires the new SolidityError code-3 behavior from hardhat's JSON-RPC handler"
}
]
}
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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,43 @@ import type { Hex } from "viem";

import { assertHardhatInvariant } from "@nomicfoundation/hardhat-errors";
import { ensureError } from "@nomicfoundation/hardhat-utils/error";
import { isPrefixedHexString } from "@nomicfoundation/hardhat-utils/hex";
import {

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 don't get why this needs to be changed. I would have assumed that it was still working as expected.

If I revert the changes to this package, except for the package.json, the tests still pass.

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: b5e2f69

ContractFunctionExecutionError,
ContractFunctionRevertedError,
} from "viem";

/**
* Recursively extracts, if it exists, the revert data hex string from an error.
* Extracts the revert data hex string from a viem contract error.
*
* When a contract call reverts, Viem throws an `Error` whose `data` property
* contains the hex-encoded revert reason. That `data` may live on the top-level
* error or deeper in a chain of `cause` errors. This function walks down the
* `cause` chain until it finds a valid `0x` hex string.
* When a contract call reverts, viem throws a
* `ContractFunctionExecutionError` whose cause is a
* `ContractFunctionRevertedError` that carries the raw hex revert data
* in its `raw` property.
*
* @param error - The thrown Viem `Error` object, which may include a `data` field
* or nested `cause` errors carrying the revert data.
* @returns The `0x` hex string representing the revert data.
* @param error - The thrown viem `Error` object.
* @returns An object with the error `name`, `message`, and `data` (`0x`-prefixed hex revert data).
*
* @throws If no valid `0x` prefixed hex string revert data is found anywhere in the error chain.
* @throws If the error is not the expected viem revert error structure.
*/
export function extractRevertError(error: unknown): {
name: string;
message: string;
data: Hex;
} {
ensureError(error);
ensureError(error, ContractFunctionExecutionError);

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.

This change can probably be removed, but with the error code 3, the logic can be simplified. So I think it's a nice refactoring to have a cleaner code

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 the previous version feel more robust. It also doesn't force you to change the test in packages/hardhat-viem-assertions/test/internal/assertions/revert/revert.ts, which had more useful information in the previous version.

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: b5e2f69


let dataReason: Hex | undefined;
const { cause } = error;

let current: Error | undefined = error;
let message: string = "";
while (current !== undefined) {
// Traverse the cause chain to find a valid raw error reason, if one exists.
if ("data" in current) {
const { data } = current;

if (typeof data === "string" && isPrefixedHexString(data)) {
dataReason = data;
message = current.message;
}
}

/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
-- all the nested errors might contain a `cause` field */
current = current.cause as Error | undefined;
}
ensureError(cause, ContractFunctionRevertedError);

assertHardhatInvariant(
dataReason !== undefined,
`No revert data found on error.\nError name: "${error.name}", message: ${error.message}`,
cause.raw !== undefined,
`Expected raw revert data on ContractFunctionRevertedError, but got none. Error: "${cause.message}"`,
Comment thread
ChristopherDedominici marked this conversation as resolved.
Outdated
);

return {
name: error.name,
message,
data: dataReason,
message: cause.message,
data: cause.raw,
};
}
Loading
Loading