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

Get stack traces from EDR's response #6085

Merged
merged 9 commits into from
Jan 3, 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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions .changeset/forty-dogs-sit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"hardhat": patch
---

Improve solidity stack traces performance by getting them from the EDR response.
2 changes: 1 addition & 1 deletion packages/hardhat-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
"dependencies": {
"@ethersproject/abi": "^5.1.2",
"@metamask/eth-sig-util": "^4.0.0",
"@nomicfoundation/edr": "^0.6.5",
"@nomicfoundation/edr": "^0.7.0",
"@nomicfoundation/ethereumjs-common": "4.0.4",
"@nomicfoundation/ethereumjs-tx": "5.0.4",
"@nomicfoundation/ethereumjs-util": "9.0.4",
Expand Down
163 changes: 13 additions & 150 deletions packages/hardhat-core/src/internal/hardhat-network/provider/provider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type {
Artifacts,
CompilerInput,
CompilerOutput,
EIP1193Provider,
EthSubscription,
HardhatNetworkChainsConfig,
Expand All @@ -11,9 +9,6 @@ import type {
import type {
EdrContext,
Provider as EdrProviderT,
VmTraceDecoder as VmTraceDecoderT,
VMTracer as VMTracerT,
RawTrace,
Response,
SubscriptionEvent,
HttpHeader,
Expand All @@ -23,37 +18,24 @@ import picocolors from "picocolors";
import debug from "debug";
import { EventEmitter } from "events";
import fsExtra from "fs-extra";
import * as t from "io-ts";
import semver from "semver";

import { requireNapiRsModule } from "../../../common/napi-rs";
import {
HARDHAT_NETWORK_RESET_EVENT,
HARDHAT_NETWORK_REVERT_SNAPSHOT_EVENT,
} from "../../constants";
import {
rpcCompilerInput,
rpcCompilerOutput,
} from "../../core/jsonrpc/types/input/solc";
import { validateParams } from "../../core/jsonrpc/types/input/validation";
import {
InvalidArgumentsError,
InvalidInputError,
ProviderError,
} from "../../core/providers/errors";
import { isErrorResponse } from "../../core/providers/http";
import { getHardforkName } from "../../util/hardforks";
import { createModelsAndDecodeBytecodes } from "../stack-traces/compiler-to-model";
import { ConsoleLogger } from "../stack-traces/consoleLogger";
import {
VmTraceDecoder,
initializeVmTraceDecoder,
} from "../stack-traces/vm-trace-decoder";
import { FIRST_SOLC_VERSION_SUPPORTED } from "../stack-traces/constants";
import { encodeSolidityStackTrace } from "../stack-traces/solidity-errors";
import { SolidityStackTrace } from "../stack-traces/solidity-stack-trace";
import { SolidityTracer } from "../stack-traces/solidityTracer";
import { VMTracer } from "../stack-traces/vm-tracer";

import { getPackageJson } from "../../util/packageInfo";
import {
Expand Down Expand Up @@ -168,25 +150,16 @@ export class EdrProviderWrapper
// temporarily added to make smock work with HH+EDR
private _callOverrideCallback?: CallOverrideCallback;

/** Used for internal stack trace tests. */
private _vmTracer?: VMTracerT;

private constructor(
private readonly _provider: EdrProviderT,
// we add this for backwards-compatibility with plugins like solidity-coverage
private readonly _node: {
_vm: MinimalEthereumJsVm;
},
private readonly _vmTraceDecoder: VmTraceDecoderT,
// The common configuration for EthereumJS VM is not used by EDR, but tests expect it as part of the provider.
private readonly _common: Common,
tracingConfig?: TracingConfig
private readonly _common: Common
) {
super();

if (tracingConfig !== undefined) {
initializeVmTraceDecoder(this._vmTraceDecoder, tracingConfig);
}
}

public static async create(
Expand Down Expand Up @@ -238,8 +211,6 @@ export class EdrProviderWrapper
const printLineFn = loggerConfig.printLineFn ?? printLine;
const replaceLastLineFn = loggerConfig.replaceLastLineFn ?? replaceLastLine;

const vmTraceDecoder = new VmTraceDecoder();

const hardforkName = getHardforkName(config.hardfork);

const provider = await Provider.withConfig(
Expand Down Expand Up @@ -297,15 +268,6 @@ export class EdrProviderWrapper
{
enable: loggerConfig.enabled,
decodeConsoleLogInputsCallback: ConsoleLogger.getDecodedLogs,
getContractAndFunctionNameCallback: (
code: Buffer,
calldata?: Buffer
) => {
return vmTraceDecoder.getContractAndFunctionNamesForCall(
code,
calldata
);
},
printLineCallback: (message: string, replace: boolean) => {
if (replace) {
replaceLastLineFn(message);
Expand All @@ -314,6 +276,7 @@ export class EdrProviderWrapper
}
},
},
tracingConfig ?? {},
(event: SubscriptionEvent) => {
eventAdapter.emit("ethEvent", event);
}
Expand All @@ -327,9 +290,7 @@ export class EdrProviderWrapper
const wrapper = new EdrProviderWrapper(
provider,
minimalEthereumJsNode,
vmTraceDecoder,
common,
tracingConfig
common
);

// Pass through all events from the provider
Expand All @@ -350,14 +311,9 @@ export class EdrProviderWrapper

const params = args.params ?? [];

if (args.method === "hardhat_addCompilationResult") {
return this._addCompilationResultAction(
...this._addCompilationResultParams(params)
);
} else if (args.method === "hardhat_getStackTraceFailuresCount") {
return this._getStackTraceFailuresCountAction(
...this._getStackTraceFailuresCountParams(params)
);
if (args.method === "hardhat_getStackTraceFailuresCount") {
// stubbed for backwards compatibility
return 0;
}

const stringifiedArgs = JSON.stringify({
Expand All @@ -378,14 +334,11 @@ export class EdrProviderWrapper

const needsTraces =
this._node._vm.evm.events.eventNames().length > 0 ||
this._node._vm.events.eventNames().length > 0 ||
this._vmTracer !== undefined;
this._node._vm.events.eventNames().length > 0;

if (needsTraces) {
const rawTraces = responseObject.traces;
for (const rawTrace of rawTraces) {
this._vmTracer?.observe(rawTrace);

// For other consumers in JS we need to marshall the entire trace over FFI
const trace = rawTrace.trace();

Expand Down Expand Up @@ -434,13 +387,14 @@ export class EdrProviderWrapper
if (isErrorResponse(response)) {
let error;

const solidityTrace = responseObject.solidityTrace;
let stackTrace: SolidityStackTrace | undefined;
if (solidityTrace !== null) {
stackTrace = await this._rawTraceToSolidityStackTrace(solidityTrace);
let stackTrace: SolidityStackTrace | null = null;
try {
stackTrace = responseObject.stackTrace();
} catch (e) {
log("Failed to get stack trace: %O", e);
}

if (stackTrace !== undefined) {
if (stackTrace !== null) {
error = encodeSolidityStackTrace(response.error.message, stackTrace);
// Pass data and transaction hash from the original error
(error as any).data = response.error.data?.data ?? undefined;
Expand Down Expand Up @@ -482,15 +436,6 @@ export class EdrProviderWrapper
}
}

/**
* Sets a `VMTracer` that observes EVM throughout requests.
*
* Used for internal stack traces integration tests.
*/
public setVmTracer(vmTracer?: VMTracerT) {
this._vmTracer = vmTracer;
}

// temporarily added to make smock work with HH+EDR
private _setCallOverrideCallback(callback: CallOverrideCallback) {
this._callOverrideCallback = callback;
Expand Down Expand Up @@ -533,88 +478,6 @@ export class EdrProviderWrapper

this.emit("message", message);
}

private _addCompilationResultParams(
params: any[]
): [string, CompilerInput, CompilerOutput] {
return validateParams(
params,
t.string,
rpcCompilerInput,
rpcCompilerOutput
);
}

private async _addCompilationResultAction(
solcVersion: string,
compilerInput: CompilerInput,
compilerOutput: CompilerOutput
): Promise<boolean> {
let bytecodes;
try {
bytecodes = createModelsAndDecodeBytecodes(
solcVersion,
compilerInput,
compilerOutput
);
} catch (error) {
console.warn(
picocolors.yellow(
"The Hardhat Network tracing engine could not be updated. Run Hardhat with --verbose to learn more."
)
);

log(
"VmTraceDecoder failed to be updated. Please report this to help us improve Hardhat.\n",
error
);

return false;
}

for (const bytecode of bytecodes) {
this._vmTraceDecoder.addBytecode(bytecode);
}

return true;
}

private _getStackTraceFailuresCountParams(params: any[]): [] {
return validateParams(params);
}

private _getStackTraceFailuresCountAction(): number {
return this._failedStackTraces;
}

private async _rawTraceToSolidityStackTrace(
rawTrace: RawTrace
): Promise<SolidityStackTrace | undefined> {
const vmTracer = new VMTracer();
vmTracer.observe(rawTrace);

let vmTrace = vmTracer.getLastTopLevelMessageTrace();
const vmTracerError = vmTracer.getLastError();

if (vmTrace !== undefined) {
vmTrace = this._vmTraceDecoder.tryToDecodeMessageTrace(vmTrace);
}

try {
if (vmTrace === undefined || vmTracerError !== undefined) {
throw vmTracerError;
}

const solidityTracer = new SolidityTracer();
return solidityTracer.getStackTrace(vmTrace);
} catch (err) {
this._failedStackTraces += 1;
log(
"Could not generate stack trace. Please report this to help us improve Hardhat.\n",
err
);
}
}
}

async function clientVersion(edrClientVersion: string): Promise<string> {
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading
Loading