Skip to content

Commit

Permalink
Run solidity tracing logic in EDR (#5769)
Browse files Browse the repository at this point in the history
* refactor: Remove the experimental MessageTrace hook

This removes the `MessageTrace` from the (experimental) public API and
only hides it behind our internal API, where it is used for stack trace
logic.

We want to move this type to Rust to decrease the overhead of the value
passing across the FFI and it would allow us to remove/not export this
type entirely from Rust when porting the stack trace logic.

* Add await in provider creation

* refactor: Use the new EDR internals for the stack trace decoding

* refactor: Merge ContractsIdentifier into VmTraceDecoder

* refactor: Adapt to the now removed Bytecode

* fix: Use nested scopes in a stack trace entry message switch

* Bump EDR version

* Create happy-kiwis-draw.md

---------

Co-authored-by: Igor Matuszewski <[email protected]>
  • Loading branch information
fvictorio and Xanewok authored Sep 23, 2024
1 parent 40d9339 commit 5fb3095
Show file tree
Hide file tree
Showing 23 changed files with 220 additions and 5,311 deletions.
5 changes: 5 additions & 0 deletions .changeset/happy-kiwis-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"hardhat": patch
---

Adapted Hardhat to trace Solidity logic on EDR. This resulted in a 10% performance improvement for most workloads.
2 changes: 1 addition & 1 deletion packages/hardhat-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
"dependencies": {
"@ethersproject/abi": "^5.1.2",
"@metamask/eth-sig-util": "^4.0.0",
"@nomicfoundation/edr": "^0.5.2",
"@nomicfoundation/edr": "^0.6.1",
"@nomicfoundation/ethereumjs-common": "4.0.4",
"@nomicfoundation/ethereumjs-tx": "5.0.4",
"@nomicfoundation/ethereumjs-util": "9.0.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import type {
import type {
EdrContext,
Provider as EdrProviderT,
VmTraceDecoder as VmTraceDecoderT,
VMTracer as VMTracerT,
RawTrace,
Response,
SubscriptionEvent,
Expand Down Expand Up @@ -42,7 +44,6 @@ 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 { ContractsIdentifier } from "../stack-traces/contracts-identifier";
import {
VmTraceDecoder,
initializeVmTraceDecoder,
Expand Down Expand Up @@ -167,15 +168,15 @@ export class EdrProviderWrapper
private _callOverrideCallback?: CallOverrideCallback;

/** Used for internal stack trace tests. */
private _vmTracer?: VMTracer;
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: VmTraceDecoder,
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
Expand Down Expand Up @@ -221,8 +222,7 @@ export class EdrProviderWrapper
const printLineFn = loggerConfig.printLineFn ?? printLine;
const replaceLastLineFn = loggerConfig.replaceLastLineFn ?? replaceLastLine;

const contractsIdentifier = new ContractsIdentifier();
const vmTraceDecoder = new VmTraceDecoder(contractsIdentifier);
const vmTraceDecoder = new VmTraceDecoder();

const hardforkName = getHardforkName(config.hardfork);

Expand Down Expand Up @@ -368,6 +368,9 @@ export class EdrProviderWrapper
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();

// beforeTx event
Expand All @@ -384,8 +387,6 @@ export class EdrProviderWrapper
edrTracingStepToMinimalInterpreterStep(traceItem)
);
}

this._vmTracer?.addStep(traceItem);
}
// afterMessage event
else if ("executionResult" in traceItem) {
Expand All @@ -395,8 +396,6 @@ export class EdrProviderWrapper
edrTracingMessageResultToMinimalEVMResult(traceItem)
);
}

this._vmTracer?.addAfterMessage(traceItem.executionResult);
}
// beforeMessage event
else {
Expand All @@ -406,8 +405,6 @@ export class EdrProviderWrapper
edrTracingMessageToMinimalMessage(traceItem)
);
}

this._vmTracer?.addBeforeMessage(traceItem);
}
}

Expand Down Expand Up @@ -474,7 +471,7 @@ export class EdrProviderWrapper
*
* Used for internal stack traces integration tests.
*/
public setVmTracer(vmTracer?: VMTracer) {
public setVmTracer(vmTracer?: VMTracerT) {
this._vmTracer = vmTracer;
}

Expand Down Expand Up @@ -552,7 +549,7 @@ export class EdrProviderWrapper
);

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

Expand All @@ -578,17 +575,7 @@ export class EdrProviderWrapper
rawTrace: RawTrace
): Promise<SolidityStackTrace | undefined> {
const vmTracer = new VMTracer();

const trace = rawTrace.trace();
for (const traceItem of trace) {
if ("pc" in traceItem) {
vmTracer.addStep(traceItem);
} else if ("executionResult" in traceItem) {
vmTracer.addAfterMessage(traceItem.executionResult);
} else {
vmTracer.addBeforeMessage(traceItem);
}
}
vmTracer.observe(rawTrace);

let vmTrace = vmTracer.getLastTopLevelMessageTrace();
const vmTracerError = vmTracer.getLastError();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,75 +1,7 @@
import { bytesToBigInt } from "@nomicfoundation/ethereumjs-util";
import { assertHardhatInvariant } from "../../core/errors";
import { requireNapiRsModule } from "../../../common/napi-rs";

const { rawDecode } = require("ethereumjs-abi");
const { ReturnData } = requireNapiRsModule(
"@nomicfoundation/edr"
) as typeof import("@nomicfoundation/edr");

// selector of Error(string)
const ERROR_SELECTOR = "08c379a0";
// selector of Panic(uint256)
const PANIC_SELECTOR = "4e487b71";

/**
* Represents the returnData of a transaction, whose contents are unknown.
*/
export class ReturnData {
private _selector: string | undefined;

constructor(public value: Uint8Array) {
if (value.length >= 4) {
this._selector = Buffer.from(value.slice(0, 4)).toString("hex");
}
}

public isEmpty(): boolean {
return this.value.length === 0;
}

public matchesSelector(selector: Uint8Array): boolean {
if (this._selector === undefined) {
return false;
}

return this._selector === Buffer.from(selector).toString("hex");
}

public isErrorReturnData(): boolean {
return this._selector === ERROR_SELECTOR;
}

public isPanicReturnData(): boolean {
return this._selector === PANIC_SELECTOR;
}

public decodeError(): string {
if (this.isEmpty()) {
return "";
}

assertHardhatInvariant(
this._selector === ERROR_SELECTOR,
"Expected return data to be a Error(string)"
);

const [decodedError] = rawDecode(["string"], this.value.slice(4)) as [
string
];

return decodedError;
}

public decodePanic(): bigint {
assertHardhatInvariant(
this._selector === PANIC_SELECTOR,
"Expected return data to be a Panic(uint256)"
);

// we are assuming that panic codes are smaller than Number.MAX_SAFE_INTEGER
const errorCode = bytesToBigInt(this.value.slice(4));

return errorCode;
}

public getSelector(): string | undefined {
return this._selector;
}
}
export { ReturnData };
Original file line number Diff line number Diff line change
@@ -1,95 +1,7 @@
import type { ExceptionalHalt, SuccessReason } from "@nomicfoundation/edr";

import { requireNapiRsModule } from "../../../../common/napi-rs";

export enum ExitCode {
SUCCESS,
REVERT,
OUT_OF_GAS,
INTERNAL_ERROR,
INVALID_OPCODE,
STACK_UNDERFLOW,
CODESIZE_EXCEEDS_MAXIMUM,
CREATE_COLLISION,
STATIC_STATE_CHANGE,
}

export class Exit {
public static fromEdrSuccessReason(reason: SuccessReason): Exit {
const { SuccessReason } = requireNapiRsModule(
"@nomicfoundation/edr"
) as typeof import("@nomicfoundation/edr");

switch (reason) {
case SuccessReason.Stop:
case SuccessReason.Return:
case SuccessReason.SelfDestruct:
case SuccessReason.EofReturnContract:
return new Exit(ExitCode.SUCCESS);
}

const _exhaustiveCheck: never = reason;
}

public static fromEdrExceptionalHalt(halt: ExceptionalHalt): Exit {
const { ExceptionalHalt } = requireNapiRsModule(
"@nomicfoundation/edr"
) as typeof import("@nomicfoundation/edr");

switch (halt) {
case ExceptionalHalt.OutOfGas:
return new Exit(ExitCode.OUT_OF_GAS);

case ExceptionalHalt.OpcodeNotFound:
case ExceptionalHalt.InvalidFEOpcode:
// Returned when an opcode is not implemented for the hardfork
case ExceptionalHalt.NotActivated:
return new Exit(ExitCode.INVALID_OPCODE);

case ExceptionalHalt.StackUnderflow:
return new Exit(ExitCode.STACK_UNDERFLOW);

case ExceptionalHalt.CreateCollision:
return new Exit(ExitCode.CREATE_COLLISION);

case ExceptionalHalt.CreateContractSizeLimit:
return new Exit(ExitCode.CODESIZE_EXCEEDS_MAXIMUM);

default: {
// eslint-disable-next-line @nomicfoundation/hardhat-internal-rules/only-hardhat-error
throw new Error(`Unmatched EDR exceptional halt: ${halt}`);
}
}
}

constructor(public kind: ExitCode) {}

public isError(): boolean {
return this.kind !== ExitCode.SUCCESS;
}

public getReason(): string {
switch (this.kind) {
case ExitCode.SUCCESS:
return "Success";
case ExitCode.REVERT:
return "Reverted";
case ExitCode.OUT_OF_GAS:
return "Out of gas";
case ExitCode.INTERNAL_ERROR:
return "Internal error";
case ExitCode.INVALID_OPCODE:
return "Invalid opcode";
case ExitCode.STACK_UNDERFLOW:
return "Stack underflow";
case ExitCode.CODESIZE_EXCEEDS_MAXIMUM:
return "Codesize exceeds maximum";
case ExitCode.CREATE_COLLISION:
return "Create collision";
case ExitCode.STATIC_STATE_CHANGE:
return "Static state change";
}
const { ExitCode } = requireNapiRsModule(
"@nomicfoundation/edr"
) as typeof import("@nomicfoundation/edr");

const _exhaustiveCheck: never = this.kind;
}
}
export { ExitCode };
Loading

0 comments on commit 5fb3095

Please sign in to comment.