diff --git a/.changeset/cuddly-cycles-drum.md b/.changeset/cuddly-cycles-drum.md new file mode 100644 index 00000000000..a5ed986368e --- /dev/null +++ b/.changeset/cuddly-cycles-drum.md @@ -0,0 +1,5 @@ +--- +"hardhat": patch +--- + +Fixed bug to preserve revert data in JSON-RPC responses for non-ProviderErrors ([8061](https://github.com/NomicFoundation/hardhat/pull/8061)). diff --git a/v-next/hardhat/src/internal/builtin-plugins/node/json-rpc/handler.ts b/v-next/hardhat/src/internal/builtin-plugins/node/json-rpc/handler.ts index 3f0863a4077..a17f1da8155 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/node/json-rpc/handler.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/node/json-rpc/handler.ts @@ -250,6 +250,12 @@ const _readWsRequest = (msg: string): JsonRpcRequest | JsonRpcRequest[] => { }; const _handleError = (error: Error): JsonRpcResponse => { + // Extract revert data from the original error before potentially wrapping it. + // Non-ProviderError errors (e.g. SolidityError) carry .data and .transactionHash + // that would be lost when wrapped in InternalError below. + const txHash = extractTxHash(error); + const returnData = extractReturnData(error); + // In case of non-hardhat error, treat it as internal and associate the appropriate error code. if (!ProviderError.isProviderError(error)) { error = new InternalError(undefined, error); @@ -266,8 +272,8 @@ const _handleError = (error: Error): JsonRpcResponse => { message: error.message, data: { message: error.message, - txHash: extractTxHash(error), - data: extractReturnData(error), + txHash, + data: returnData, }, }, }; diff --git a/v-next/hardhat/test/internal/builtin-plugins/node/json-rpc/handler.ts b/v-next/hardhat/test/internal/builtin-plugins/node/json-rpc/handler.ts index 2c4f74a5f7c..dc4c750989c 100644 --- a/v-next/hardhat/test/internal/builtin-plugins/node/json-rpc/handler.ts +++ b/v-next/hardhat/test/internal/builtin-plugins/node/json-rpc/handler.ts @@ -64,6 +64,15 @@ describe("JSON-RPC handler", async function () { }; throw err; }, + nonProviderErrorWithRevertData: () => { + // Simulates SolidityError: a non-ProviderError with .data and .transactionHash + const err = new Error("revert Unauthorized"); + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- allow in test + (err as any).data = "0xdeadbeef"; + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- allow in test + (err as any).transactionHash = "0xabc123"; + throw err; + }, }); const server = new JsonRpcServerImplementation({ hostname, @@ -298,6 +307,29 @@ describe("JSON-RPC handler", async function () { assert.equal(rpcRes.error.data.txHash, "0xbeef"); assert.equal(rpcRes.error.data.data, "0xabad1dea"); }); + + it("should preserve revert data from non-ProviderError errors", async function () { + // Simulates SolidityError which has .data and .transactionHash but is NOT a ProviderError + const rpcReq: JsonRpcRequest = { + jsonrpc: "2.0", + method: "nonProviderErrorWithRevertData", + id: 1, + }; + + const rpcRes = await postRawJsonRpc(hostname, port, JSON.stringify(rpcReq)); + + assert.ok( + isJsonRpcResponse(rpcRes) && isFailedJsonRpcResponse(rpcRes), + "Expected a failed JSON-RPC response", + ); + assert.equal(rpcRes.error.code, InternalError.CODE); + assert.ok( + isObject(rpcRes.error.data), + "Expected error data to be an object", + ); + assert.equal(rpcRes.error.data.txHash, "0xabc123"); + assert.equal(rpcRes.error.data.data, "0xdeadbeef"); + }); }); async function postRawJsonRpc(