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

Run solidity tracing logic in EDR #5769

Merged
merged 9 commits into from
Sep 23, 2024
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