diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/provider.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/provider.ts index d37ebadf8a..d82224ed16 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/provider.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/provider.ts @@ -67,12 +67,16 @@ import { } from "./node-types"; import { edrRpcDebugTraceToHardhat, + edrTracingMessageResultToMinimalEVMResult, + edrTracingMessageToMinimalMessage, + edrTracingStepToMinimalInterpreterStep, ethereumjsIntervalMiningConfigToEdr, ethereumjsMempoolOrderToEdrMineOrdering, ethereumsjsHardforkToEdrSpecId, } from "./utils/convertToEdr"; import { makeCommon } from "./utils/makeCommon"; import { LoggerConfig, printLine, replaceLastLine } from "./modules/logger"; +import { MinimalEthereumJsVm, getMinimalEthereumJsVm } from "./vm/minimal-vm"; const log = debug("hardhat:core:hardhat-network:provider"); @@ -159,6 +163,10 @@ export class EdrProviderWrapper private constructor( private readonly _provider: EdrProviderT, + // we add this for backwards-compatibility with plugins like solidity-coverage + private readonly _node: { + _vm: MinimalEthereumJsVm; + }, private readonly _eventAdapter: EdrProviderEventAdapter, private readonly _vmTraceDecoder: VmTraceDecoder, private readonly _rawTraceCallbacks: RawTraceCallbacks, @@ -291,9 +299,14 @@ export class EdrProviderWrapper } ); + const minimalEthereumJsNode = { + _vm: getMinimalEthereumJsVm(provider), + }; + const common = makeCommon(getNodeConfig(config)); const wrapper = new EdrProviderWrapper( provider, + minimalEthereumJsNode, eventAdapter, vmTraceDecoder, rawTraceCallbacks, @@ -344,16 +357,28 @@ export class EdrProviderWrapper const trace = rawTrace.trace(); for (const traceItem of trace) { if ("pc" in traceItem) { + this._node._vm.evm.events.emit( + "step", + edrTracingStepToMinimalInterpreterStep(traceItem) + ); if (this._rawTraceCallbacks.onStep !== undefined) { await this._rawTraceCallbacks.onStep(traceItem); } } else if ("executionResult" in traceItem) { + this._node._vm.evm.events.emit( + "afterMessage", + edrTracingMessageResultToMinimalEVMResult(traceItem) + ); if (this._rawTraceCallbacks.onAfterMessage !== undefined) { await this._rawTraceCallbacks.onAfterMessage( traceItem.executionResult ); } } else { + this._node._vm.evm.events.emit( + "beforeMessage", + edrTracingMessageToMinimalMessage(traceItem) + ); if (this._rawTraceCallbacks.onBeforeMessage !== undefined) { await this._rawTraceCallbacks.onBeforeMessage(traceItem); } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToEdr.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToEdr.ts index b68e87b6e2..9f1ab2498e 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToEdr.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToEdr.ts @@ -3,10 +3,20 @@ import { MineOrdering, IntervalRange, DebugTraceResult, + TracingMessage, + TracingMessageResult, + TracingStep, } from "@ignored/edr"; +import { Address } from "@nomicfoundation/ethereumjs-util"; + import { HardforkName } from "../../../util/hardforks"; import { IntervalMiningConfig, MempoolOrder } from "../node-types"; import { RpcDebugTraceOutput, RpcStructLog } from "../output"; +import { + MinimalEVMResult, + MinimalInterpreterStep, + MinimalMessage, +} from "../vm/types"; /* eslint-disable @nomicfoundation/hardhat-internal-rules/only-hardhat-error */ @@ -181,3 +191,38 @@ export function edrRpcDebugTraceToHardhat( structLogs, }; } + +export function edrTracingStepToMinimalInterpreterStep( + step: TracingStep +): MinimalInterpreterStep { + return { + pc: Number(step.pc), + depth: step.depth, + opcode: { + name: step.opcode, + }, + stack: step.stackTop !== undefined ? [step.stackTop] : [], + }; +} + +export function edrTracingMessageResultToMinimalEVMResult( + tracingMessageResult: TracingMessageResult +): MinimalEVMResult { + return { + execResult: { + executionGasUsed: tracingMessageResult.executionResult.result.gasUsed, + }, + }; +} + +export function edrTracingMessageToMinimalMessage( + message: TracingMessage +): MinimalMessage { + return { + to: message.to !== undefined ? new Address(message.to) : undefined, + data: message.data, + value: message.value, + caller: new Address(message.caller), + gasLimit: message.gasLimit, + }; +} diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/minimal-vm.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/minimal-vm.ts new file mode 100644 index 0000000000..b5ed721064 --- /dev/null +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/minimal-vm.ts @@ -0,0 +1,101 @@ +import type { Provider as EdrProviderT } from "@ignored/edr"; +import type { Address } from "@nomicfoundation/ethereumjs-util"; +import type { + MinimalEVMResult, + MinimalInterpreterStep, + MinimalMessage, +} from "./types"; + +import { AsyncEventEmitter } from "@nomicfoundation/ethereumjs-util"; + +/** + * Used by the provider to keep the `_vm` variable used by some plugins. This + * interface only has the things used by those plugins. + */ +export interface MinimalEthereumJsVm { + evm: { + events: AsyncEventEmitter; + }; + stateManager: { + putContractCode: (address: Address, code: Buffer) => Promise; + getContractStorage: (address: Address, slotHash: Buffer) => Promise; + putContractStorage: ( + address: Address, + slotHash: Buffer, + slotValue: Buffer + ) => Promise; + }; +} + +// we need to use a type instead of an interface to satisfy the type constarint +// of the AsyncEventEmitter type param +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +type MinimalEthereumJsVmEvents = { + beforeMessage: ( + data: MinimalMessage, + resolve?: (result?: any) => void + ) => void; + afterMessage: ( + data: MinimalEVMResult, + resolve?: (result?: any) => void + ) => void; + step: ( + data: MinimalInterpreterStep, + resolve?: (result?: any) => void + ) => void; +}; + +export class MinimalEthereumJsVmEventEmitter extends AsyncEventEmitter {} + +export function getMinimalEthereumJsVm( + provider: EdrProviderT +): MinimalEthereumJsVm { + const minimalEthereumJsVm: MinimalEthereumJsVm = { + evm: { + events: new MinimalEthereumJsVmEventEmitter(), + }, + stateManager: { + putContractCode: async (address: Address, code: Buffer) => { + await provider.handleRequest( + JSON.stringify({ + method: "hardhat_setCode", + params: [`0x${address.toString()}`, `0x${code.toString("hex")}`], + }) + ); + }, + getContractStorage: async (address: Address, slotHash: Buffer) => { + const responseObject = await provider.handleRequest( + JSON.stringify({ + method: "eth_getStorageAt", + params: [ + `0x${address.toString()}`, + `0x${slotHash.toString("hex")}`, + ], + }) + ); + + const response = JSON.parse(responseObject.json); + + return Buffer.from(response.result.slice(2), "hex"); + }, + putContractStorage: async ( + address: Address, + slotHash: Buffer, + slotValue: Buffer + ) => { + await provider.handleRequest( + JSON.stringify({ + method: "hardhat_setStorageAt", + params: [ + `0x${address.toString()}`, + `0x${slotHash.toString("hex")}`, + `0x${slotValue.toString("hex")}`, + ], + }) + ); + }, + }, + }; + + return minimalEthereumJsVm; +} diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/proxy-vm.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/proxy-vm.ts deleted file mode 100644 index 15d3b7c33b..0000000000 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/proxy-vm.ts +++ /dev/null @@ -1,123 +0,0 @@ -import type { Address } from "@nomicfoundation/ethereumjs-util"; - -import type { EVMResult, Message } from "./types"; - -export interface MinimalInterpreterStep { - pc: number; - depth: number; - opcode: { - name: string; - }; - stack: bigint[]; -} - -declare function onEvent( - event: "step", - cb: (step: MinimalInterpreterStep, next: any) => Promise -): void; -declare function onEvent( - event: "beforeMessage", - cb: (step: Message, next: any) => Promise -): void; -declare function onEvent( - event: "afterMessage", - cb: (step: EVMResult, next: any) => Promise -): void; - -/** - * Used by the node to keep the `_vm` variable used by some plugins. This - * interface only has the things used by those plugins. - */ -export interface MinimalEthereumJsVm { - evm: { - events: { - on: typeof onEvent; - }; - }; - stateManager: { - putContractCode: (address: Address, code: Buffer) => Promise; - getContractStorage: (address: Address, slotHash: Buffer) => Promise; - putContractStorage: ( - address: Address, - slotHash: Buffer, - slotValue: Buffer - ) => Promise; - }; -} - -/** - * Creates a proxy for the given object that throws if a property is accessed - * that is not in the original object. It also works for nested objects. - */ -// function createVmProxy(obj: T, prefix?: string): T { -// if (typeof obj !== "object" || obj === null) { -// return obj; -// } - -// return new Proxy(obj, { -// get(target, prop): any { -// if (prop in target) { -// return createVmProxy( -// (target as any)[prop], -// `${prefix ?? ""}${String(prop)}.` -// ); -// } - -// assertHardhatInvariant( -// false, -// `Property ${prefix ?? ""}${String(prop)} cannot be used in node._vm` -// ); -// }, - -// set() { -// assertHardhatInvariant(false, "Properties cannot be changed in node._vm"); -// }, -// }); -// } - -// TODO: https://github.com/NomicFoundation/edr/issues/48 -// Adapt this to EdrProviderWrapper -// export function getMinimalEthereumJsVm( -// context: EthContextAdapter -// ): MinimalEthereumJsVm { -// const minimalEthereumJsVm: MinimalEthereumJsVm = { -// evm: { -// events: { -// on: (event, cb) => { -// assertHardhatInvariant( -// event === "step" || -// event === "beforeMessage" || -// event === "afterMessage", -// `Event ${event} is not supported in node._vm` -// ); - -// if (event === "step") { -// context.vm().onStep(cb as any); -// context.blockMiner().onStep(cb as any); -// } else if (event === "beforeMessage") { -// context.vm().onBeforeMessage(cb as any); -// context.blockMiner().onBeforeMessage(cb as any); -// } else if (event === "afterMessage") { -// context.vm().onAfterMessage(cb as any); -// context.blockMiner().onAfterMessage(cb as any); -// } -// }, -// }, -// }, -// stateManager: { -// putContractCode: async (address, code) => { -// return context.vm().putContractCode(address, code, true); -// }, -// getContractStorage: async (address, slotHash) => { -// return context.vm().getContractStorage(address, slotHash); -// }, -// putContractStorage: async (address, slotHash, slotValue) => { -// return context -// .vm() -// .putContractStorage(address, slotHash, slotValue, true); -// }, -// }, -// }; - -// return createVmProxy(minimalEthereumJsVm); -// } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/types.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/types.ts index 522e1fe8a4..16653be177 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/types.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/types.ts @@ -1,44 +1,31 @@ import type { Address } from "@nomicfoundation/ethereumjs-util"; -export interface ExecResult { - gas?: bigint; - executionGasUsed: bigint; - returnValue: Uint8Array; - selfdestruct?: Set; - createdAddresses?: Set; - gasRefund?: bigint; - blobGasUsed?: bigint; +/** + * These types are minimal versions of the values returned by ethereumjs + * in the event listeners. + */ + +export interface MinimalInterpreterStep { + pc: number; + depth: number; + opcode: { + name: string; + }; + stack: bigint[]; +} - // Commented out to simplify the types; - // we'll have to enable them if some plugin needs them: - // - // runState?: RunState; - // exceptionError?: EvmError; - // logs?: Log[]; +export interface MinimalExecResult { + executionGasUsed: bigint; } -export interface EVMResult { - createdAddress?: Address; - execResult: ExecResult; +export interface MinimalEVMResult { + execResult: MinimalExecResult; } -export interface Message { +export interface MinimalMessage { to?: Address; value: bigint; + data: Uint8Array; caller: Address; gasLimit: bigint; - data: Uint8Array; - depth: number; - code?: Uint8Array; - _codeAddress?: Address; - isStatic: boolean; - isCompiled: boolean; - salt?: Uint8Array; - containerCode?: Uint8Array /** container code for EOF1 contracts - used by CODECOPY/CODESIZE */; - selfdestruct?: Set; - createdAddresses?: Set; - delegatecall: boolean; - authcallOrigin?: Address; - gasRefund: bigint; // Keeps track of the gasRefund at the start of the frame (used for journaling purposes) - blobVersionedHashes?: Uint8Array[]; }