Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: cleanup provider and stack traces logic #6164

Merged
merged 8 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions v-next/hardhat-errors/src/descriptors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,13 @@ Try using another mnemonic or deriving less keys.`,
websiteDescription:
"The transaction to the null address cannot have undefined data",
},
PROVIDER_CLOSED: {
number: 722,
messageTemplate: "The provider has been closed.",
websiteTitle: "Provider closed",
websiteDescription:
"The provider your are trying to use has been closed. Please create a new one using hre.network.connect() and try again.",
},
},
KEYSTORE: {
INVALID_KEYSTORE_FILE_FORMAT: {
Expand Down
6 changes: 2 additions & 4 deletions v-next/hardhat-utils/src/hex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export function hexStringToNumber(hexString: string): number {
* @returns PrefixedHexString The hexadecimal representation of the bytes.
*/
export function bytesToHexString(bytes: Uint8Array): PrefixedHexString {
return `0x${Buffer.from(bytes).toString("hex")}`;
return getPrefixedHexString(Buffer.from(bytes).toString("hex"));
}

/**
Expand Down Expand Up @@ -141,9 +141,7 @@ export function normalizeHexString(hexString: string): PrefixedHexString {
);
}

return isPrefixedHexString(normalizedHexString)
? normalizedHexString
: `0x${normalizedHexString}`;
return getPrefixedHexString(normalizedHexString);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { TracingConfig } from "./types/node-types.js";
import type { EdrNetworkConfig } from "../../../../types/config.js";
import type {
EthSubscription,
FailedJsonRpcResponse,
JsonRpcResponse,
RequestArguments,
SuccessfulJsonRpcResponse,
Expand All @@ -15,7 +14,6 @@ import type {
SubscriptionEvent,
Response,
Provider,
DebugTraceResult,
ProviderConfig,
} from "@ignored/edr-optimism";

Expand All @@ -25,25 +23,27 @@ import {
l1GenesisState,
l1HardforkFromString,
} from "@ignored/edr-optimism";
import {
assertHardhatInvariant,
HardhatError,
} from "@ignored/hardhat-vnext-errors";
import { toSeconds } from "@ignored/hardhat-vnext-utils/date";
import { numberToHexString } from "@ignored/hardhat-vnext-utils/hex";
import { deepEqual } from "@ignored/hardhat-vnext-utils/lang";
import debug from "debug";

import {
EDR_NETWORK_RESET_EVENT,
EDR_NETWORK_REVERT_SNAPSHOT_EVENT,
} from "../../../constants.js";
import { EDR_NETWORK_REVERT_SNAPSHOT_EVENT } from "../../../constants.js";
import { DEFAULT_HD_ACCOUNTS_CONFIG_PARAMS } from "../accounts/constants.js";
import { BaseProvider } from "../base-provider.js";
import { getJsonRpcRequest, isFailedJsonRpcResponse } from "../json-rpc.js";

import { getGlobalEdrContext } from "./edr-context.js";
import { InvalidArgumentsError, ProviderError } from "./errors.js";
import { createSolidityErrorWithStackTrace } from "./stack-traces/stack-trace-solidity-errors.js";
import {
InvalidArgumentsError,
InvalidInputError,
ProviderError,
} from "./errors.js";
import { encodeSolidityStackTrace } from "./stack-traces/stack-trace-solidity-errors.js";
isDebugTraceResult,
isEdrProviderErrorData,
} from "./type-validation.js";
import { clientVersion } from "./utils/client-version.js";
import { ConsoleLogger } from "./utils/console-logger.js";
import {
Expand Down Expand Up @@ -116,9 +116,9 @@ interface EdrProviderConfig {
}

export class EdrProvider extends BaseProvider {
readonly #provider: Readonly<Provider>;
readonly #jsonRpcRequestWrapper?: JsonRpcRequestWrapperFunction;

#provider: Provider | undefined;
#nextRequestId = 1;

/**
Expand Down Expand Up @@ -177,31 +177,35 @@ export class EdrProvider extends BaseProvider {
super();

this.#provider = provider;

this.#jsonRpcRequestWrapper = jsonRpcRequestWrapper;
}

public async request(args: RequestArguments): Promise<unknown> {
if (args.params !== undefined && !Array.isArray(args.params)) {
// eslint-disable-next-line no-restricted-syntax -- TODO: review whether this should be a HH error
throw new InvalidInputError(
"Hardhat Network doesn't support JSON-RPC params sent as an object",
);
public async request(
requestArguments: RequestArguments,
): Promise<SuccessfulJsonRpcResponse["result"]> {
if (this.#provider === undefined) {
throw new HardhatError(HardhatError.ERRORS.NETWORK.PROVIDER_CLOSED);
}

const params = args.params ?? [];
const { method, params } = requestArguments;

const jsonRpcRequest = getJsonRpcRequest(
this.#nextRequestId++,
args.method,
method,
params,
);

let jsonRpcResponse: JsonRpcResponse;

if (this.#jsonRpcRequestWrapper !== undefined) {
jsonRpcResponse = await this.#jsonRpcRequestWrapper(
jsonRpcRequest,
async (request) => {
assertHardhatInvariant(
this.#provider !== undefined,
"The provider is not defined",
);

const stringifiedArgs = JSON.stringify(request);
const edrResponse =
await this.#provider.handleRequest(stringifiedArgs);
Expand All @@ -216,12 +220,6 @@ export class EdrProvider extends BaseProvider {
jsonRpcResponse = await this.#handleEdrResponse(edrResponse);
}

if (args.method === "hardhat_reset") {
this.emit(EDR_NETWORK_RESET_EVENT);
} else if (args.method === "evm_revert") {
this.emit(EDR_NETWORK_REVERT_SNAPSHOT_EVENT);
}

// this can only happen if a wrapper doesn't call the default
// behavior as the default throws on FailedJsonRpcResponse
if (isFailedJsonRpcResponse(jsonRpcResponse)) {
Expand All @@ -235,30 +233,35 @@ export class EdrProvider extends BaseProvider {
throw error;
}

if (jsonRpcRequest.method === "evm_revert") {
this.emit(EDR_NETWORK_REVERT_SNAPSHOT_EVENT);
}

// Override EDR version string with Hardhat version string with EDR backend,
// e.g. `HardhatNetwork/2.19.0/@ignored/edr-optimism/0.2.0-dev`
if (args.method === "web3_clientVersion") {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TODO
return clientVersion(jsonRpcResponse.result as string);
if (jsonRpcRequest.method === "web3_clientVersion") {
assertHardhatInvariant(
typeof jsonRpcResponse.result === "string",
"Invalid client version response",
);
return clientVersion(jsonRpcResponse.result);
} else if (
args.method === "debug_traceTransaction" ||
args.method === "debug_traceCall"
jsonRpcRequest.method === "debug_traceTransaction" ||
jsonRpcRequest.method === "debug_traceCall"
) {
return edrRpcDebugTraceToHardhat(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TODO
jsonRpcResponse.result as DebugTraceResult,
assertHardhatInvariant(
isDebugTraceResult(jsonRpcResponse.result),
"Invalid debug trace response",
);
return edrRpcDebugTraceToHardhat(jsonRpcResponse.result);
} else {
return jsonRpcResponse.result;
}
}

public async close(): Promise<void> {
// TODO: what needs cleaned up?
}

#isErrorResponse(response: any): response is FailedJsonRpcResponse {
return typeof response.error !== "undefined";
// Clear the provider reference to help with garbage collection
this.#provider = undefined;
}

async #handleEdrResponse(
Expand All @@ -272,7 +275,8 @@ export class EdrProvider extends BaseProvider {
jsonRpcResponse = edrResponse.data;
}

if (this.#isErrorResponse(jsonRpcResponse)) {
if (isFailedJsonRpcResponse(jsonRpcResponse)) {
const responseError = jsonRpcResponse.error;
let error;

let stackTrace: SolidityStackTrace | null = null;
Expand All @@ -283,50 +287,45 @@ export class EdrProvider extends BaseProvider {
}

if (stackTrace !== null) {
error = encodeSolidityStackTrace(
jsonRpcResponse.error.message,
stackTrace,
// If we have a stack trace, we know that the json rpc response data
// is an object with the data and transactionHash fields
assertHardhatInvariant(
isEdrProviderErrorData(responseError.data),
"Invalid error data",
);

// Pass data and transaction hash from the original error
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TODO: can we improve this `any
(error as any).data =
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TODO: can we improve this `any
(jsonRpcResponse as any).error.data?.data ?? undefined;

// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TODO: can we improve this `any`
(error as any).transactionHash =
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TODO: this really needs fixed
(jsonRpcResponse as any).error.data?.transactionHash ?? undefined;
error = createSolidityErrorWithStackTrace(
responseError.message,
stackTrace,
responseError.data.data,
responseError.data.transactionHash,
);
} else {
if (jsonRpcResponse.error.code === InvalidArgumentsError.CODE) {
error = new InvalidArgumentsError(jsonRpcResponse.error.message);
} else {
error = new ProviderError(
jsonRpcResponse.error.message,
jsonRpcResponse.error.code,
);
}
error.data = jsonRpcResponse.error.data;
error =
responseError.code === InvalidArgumentsError.CODE
? new InvalidArgumentsError(responseError.message)
: new ProviderError(responseError.message, responseError.code);
error.data = responseError.data;
}

// eslint-disable-next-line no-restricted-syntax -- we may throw non-Hardaht errors inside of an EthereumProvider
/* eslint-disable-next-line no-restricted-syntax -- we may throw
non-Hardaht errors inside of an EthereumProvider */
throw error;
}

return jsonRpcResponse;
}

public onSubscriptionEvent(event: SubscriptionEvent): void {
const subscription = `0x${event.filterId.toString(16)}`;
const subscription = numberToHexString(event.filterId);
const results = Array.isArray(event.result) ? event.result : [event.result];
for (const result of results) {
this.#emitLegacySubscriptionEvent(subscription, result);
this.#emitEip1193SubscriptionEvent(subscription, result);
}
}

#emitLegacySubscriptionEvent(subscription: string, result: any) {
#emitLegacySubscriptionEvent(subscription: string, result: unknown) {
this.emit("notification", {
subscription,
result,
Expand All @@ -341,7 +340,6 @@ export class EdrProvider extends BaseProvider {
result,
},
};

this.emit("message", message);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,6 @@ export class InternalError extends ProviderError {
}
}

export class InvalidInputError extends ProviderError {
public static readonly CODE = -32000;

constructor(message: string, parent?: Error) {
super(message, InvalidInputError.CODE, parent);
}
}

export class TransactionExecutionError extends ProviderError {
public static readonly CODE = -32003;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { numberToHexString } from "@ignored/hardhat-vnext-utils/hex";

export function panicErrorCodeToMessage(errorCode: bigint): string {
const reason = panicErrorCodeToReason(errorCode);

if (reason !== undefined) {
return `reverted with panic code 0x${errorCode.toString(16)} (${reason})`;
return `reverted with panic code ${numberToHexString(errorCode)} (${reason})`;
}

return `reverted with unknown panic code 0x${errorCode.toString(16)}`;
return `reverted with unknown panic code ${numberToHexString(errorCode)}`;
}

function panicErrorCodeToReason(errorCode: bigint): string | undefined {
Expand Down
Loading
Loading