diff --git a/packages/bugc/package.json b/packages/bugc/package.json index 28ea0f283..84723504b 100644 --- a/packages/bugc/package.json +++ b/packages/bugc/package.json @@ -75,12 +75,8 @@ "author": "", "license": "MIT", "devDependencies": { + "@ethdebug/evm": "^0.1.0-0", "@ethdebug/pointers": "^0.1.0-0", - "@ethereumjs/common": "^10.0.0", - "@ethereumjs/evm": "^10.0.0", - "@ethereumjs/statemanager": "^10.0.0", - "@ethereumjs/util": "^10.0.0", - "@ethereumjs/vm": "^10.0.0", "@hyperjump/browser": "^1.2.0", "@hyperjump/json-schema": "^1.11.0", "@types/node": "^20.0.0", diff --git a/packages/bugc/test/evm/evm-executor.ts b/packages/bugc/test/evm/evm-executor.ts deleted file mode 100644 index 125f76463..000000000 --- a/packages/bugc/test/evm/evm-executor.ts +++ /dev/null @@ -1,272 +0,0 @@ -/** - * EVM Executor for testing generated bytecode - * Uses @ethereumjs/evm for in-process execution - */ - -/* eslint-disable no-console */ - -import { EVM } from "@ethereumjs/evm"; -import { SimpleStateManager } from "@ethereumjs/statemanager"; -import { Common, Mainnet } from "@ethereumjs/common"; -import { Address, Account } from "@ethereumjs/util"; -import { hexToBytes, bytesToHex } from "ethereum-cryptography/utils"; - -export interface ExecutionOptions { - value?: bigint; - data?: string; - origin?: Address; - caller?: Address; - gasLimit?: bigint; -} - -export interface ExecutionResult { - success: boolean; - gasUsed: bigint; - returnValue: Uint8Array; - logs: unknown[]; - error?: unknown; -} - -interface ExecResult { - exceptionError?: unknown; - executionGasUsed?: bigint; - returnValue?: Uint8Array; - logs?: unknown[]; -} - -interface ResultWithExec extends ExecResult { - execResult?: ExecResult; -} - -export class EvmExecutor { - private evm: EVM; - private stateManager: SimpleStateManager; - private contractAddress: Address; - private deployerAddress: Address; - - constructor() { - const common = new Common({ - chain: Mainnet, - hardfork: "shanghai", - }); - this.stateManager = new SimpleStateManager(); - this.evm = new EVM({ - common, - stateManager: this.stateManager, - }); - - // Use a fixed contract address for testing - this.contractAddress = new Address( - hexToBytes("1234567890123456789012345678901234567890"), - ); - - // Use a fixed deployer address - this.deployerAddress = new Address( - hexToBytes("0000000000000000000000000000000000000001"), - ); - } - - /** - * Get the deployer address used for deployment - */ - getDeployerAddress(): Address { - return this.deployerAddress; - } - - /** - * Deploy bytecode to the test contract address - */ - async deploy(bytecode: string): Promise { - // Execute the constructor bytecode to get the runtime bytecode - const code = hexToBytes(bytecode); - - // Initialize deployer account - const deployerAccount = new Account(0n, BigInt(10) ** BigInt(18)); - await this.stateManager.putAccount(this.deployerAddress, deployerAccount); - - // Initialize contract account before execution - const contractAccount = new Account(0n, 0n); - await this.stateManager.putAccount(this.contractAddress, contractAccount); - - // Use runCall with empty to address to simulate CREATE - const result = await this.evm.runCall({ - caller: this.deployerAddress, - origin: this.deployerAddress, - to: undefined, // undefined 'to' means contract creation - data: code, - gasLimit: 10_000_000n, - value: 0n, - }); - - const error = result.execResult?.exceptionError; - - if (error) { - console.error("Raw error:", error); - throw new Error(`Deployment failed: ${JSON.stringify(error)}`); - } - - // Get the created contract address - const createdAddress = result.createdAddress; - if (createdAddress) { - // Update our contract address to the created one - this.contractAddress = createdAddress; - } - } - - /** - * Execute deployed bytecode - */ - async execute( - options: ExecutionOptions = {}, - trace = false, - ): Promise { - const runCallOpts = { - to: this.contractAddress, - caller: options.caller ?? this.deployerAddress, - origin: options.origin ?? this.deployerAddress, - data: options.data ? hexToBytes(options.data) : new Uint8Array(), - value: options.value ?? 0n, - gasLimit: options.gasLimit ?? 10_000_000n, - }; - - if (trace) { - this.evm.events.on( - "step", - (step: { pc: number; opcode: { name: string }; stack: bigint[] }) => { - console.log( - `[TRACE] PC=${step.pc.toString(16).padStart(4, "0")} ${step.opcode.name} stack=[${step.stack - .slice(-3) - .map((s) => s.toString(16)) - .join(", ")}]`, - ); - }, - ); - } - - const result = await this.evm.runCall(runCallOpts); - - if (trace) { - this.evm.events.removeAllListeners("step"); - } - - // Access the execution result from the returned object - const rawResult = result as ResultWithExec; - const execResult = (rawResult.execResult || rawResult) as ExecResult; - - return { - success: execResult.exceptionError === undefined, - gasUsed: execResult.executionGasUsed || 0n, - returnValue: execResult.returnValue || new Uint8Array(), - logs: execResult.logs || [], - error: execResult.exceptionError, - }; - } - - /** - * Execute bytecode directly (without deployment) - */ - async executeCode( - bytecode: string, - options: ExecutionOptions = {}, - ): Promise { - const code = hexToBytes(bytecode); - - // For storage operations to work, we need an account context - // Create a temporary account with the code - const tempAddress = new Address( - hexToBytes("9999999999999999999999999999999999999999"), - ); - await this.stateManager.putCode(tempAddress, code); - await this.stateManager.putAccount(tempAddress, new Account(0n, 0n)); - - const runCodeOpts = { - code, - data: options.data ? hexToBytes(options.data) : new Uint8Array(), - gasLimit: options.gasLimit ?? 10_000_000n, - value: options.value ?? 0n, - origin: options.origin ?? new Address(Buffer.alloc(20)), - caller: options.caller ?? new Address(Buffer.alloc(20)), - address: tempAddress, // Add the address context - }; - - const result = await this.evm.runCode(runCodeOpts); - - // Access the execution result from the returned object - const rawResult = result as ResultWithExec; - const execResult = (rawResult.execResult || rawResult) as ExecResult; - - return { - success: execResult.exceptionError === undefined, - gasUsed: execResult.executionGasUsed || 0n, - returnValue: execResult.returnValue || new Uint8Array(), - logs: execResult.logs || [], - error: execResult.exceptionError, - }; - } - - /** - * Get storage value at a specific slot - */ - async getStorage(slot: bigint): Promise { - const slotBuffer = Buffer.alloc(32); - - // Convert bigint to hex string and pad to 64 characters (32 bytes) - const hex = slot.toString(16).padStart(64, "0"); - slotBuffer.write(hex, "hex"); - - const value = await this.stateManager.getStorage( - this.contractAddress, - slotBuffer, - ); - - // Convert Uint8Array to bigint - if (value.length === 0) return 0n; - return BigInt("0x" + bytesToHex(value)); - } - - /** - * Set storage value at a specific slot - */ - async setStorage(slot: bigint, value: bigint): Promise { - const slotBuffer = Buffer.alloc(32); - slotBuffer.writeBigUInt64BE(slot, 24); - - const valueBuffer = Buffer.alloc(32); - const hex = value.toString(16).padStart(64, "0"); - valueBuffer.write(hex, "hex"); - - await this.stateManager.putStorage( - this.contractAddress, - slotBuffer, - valueBuffer, - ); - } - - /** - * Get the code at the contract address - */ - async getCode(): Promise { - return this.stateManager.getCode(this.contractAddress); - } - - /** - * Get the contract address - */ - getContractAddress(): Address { - return this.contractAddress; - } - - /** - * Reset the EVM state - */ - async reset(): Promise { - this.stateManager = new SimpleStateManager(); - this.evm = new EVM({ - common: new Common({ - chain: Mainnet, - hardfork: "shanghai", - }), - stateManager: this.stateManager, - }); - } -} diff --git a/packages/bugc/test/evm/index.ts b/packages/bugc/test/evm/index.ts index 9a8197b22..4664ff64e 100644 --- a/packages/bugc/test/evm/index.ts +++ b/packages/bugc/test/evm/index.ts @@ -1,2 +1,2 @@ -export { EvmExecutor } from "./evm-executor.js"; -export type { ExecutionOptions, ExecutionResult } from "./evm-executor.js"; +export { Executor as EvmExecutor } from "@ethdebug/evm"; +export type { ExecutionOptions, ExecutionResult } from "@ethdebug/evm"; diff --git a/packages/bugc/test/examples/index.ts b/packages/bugc/test/examples/index.ts index 60e908b47..b6c243cb1 100644 --- a/packages/bugc/test/examples/index.ts +++ b/packages/bugc/test/examples/index.ts @@ -5,4 +5,4 @@ export * from "./annotations.js"; export * from "./source-map.js"; export * from "./runners.js"; -export * from "./machine-adapter.js"; +export { createMachineState } from "@ethdebug/evm"; diff --git a/packages/bugc/test/examples/machine-adapter.ts b/packages/bugc/test/examples/machine-adapter.ts deleted file mode 100644 index 2b1e50fe6..000000000 --- a/packages/bugc/test/examples/machine-adapter.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Machine.State Adapter for EvmExecutor - * - * Implements the @ethdebug/pointers Machine.State interface - * wrapping our EvmExecutor for pointer evaluation. - */ - -import type { Machine } from "@ethdebug/pointers"; -import { Data } from "@ethdebug/pointers"; -import type { EvmExecutor } from "../evm/index.js"; - -/** - * Create a Machine.State from an EvmExecutor. - * - * This adapter allows using @ethdebug/pointers dereference() - * to evaluate pointers against our EVM executor's storage state. - * - * Note: Only storage is fully implemented. Stack, memory, etc. - * return empty/zero values since we only have end-state access. - */ -export function createMachineState(executor: EvmExecutor): Machine.State { - return { - // Trace info (not meaningful for end-state) - traceIndex: Promise.resolve(0n), - programCounter: Promise.resolve(0n), - opcode: Promise.resolve("STOP"), - - // Stack - not available in end-state - stack: { - length: Promise.resolve(0n), - peek: async (): Promise => Data.zero(), - }, - - // Memory - not available in end-state - memory: { - length: Promise.resolve(0n), - read: async (): Promise => Data.zero(), - }, - - // Storage - fully implemented via executor - storage: { - async read({ slot, slice }): Promise { - const slotValue = slot.asUint(); - const value = await executor.getStorage(slotValue); - const data = Data.fromUint(value); - - if (slice) { - const padded = data.padUntilAtLeast(32); - const sliced = new Uint8Array(padded).slice( - Number(slice.offset), - Number(slice.offset + slice.length), - ); - return Data.fromBytes(sliced); - } - - return data.padUntilAtLeast(32); - }, - }, - - // Calldata - not available - calldata: { - length: Promise.resolve(0n), - read: async (): Promise => Data.zero(), - }, - - // Returndata - not available - returndata: { - length: Promise.resolve(0n), - read: async (): Promise => Data.zero(), - }, - - // Code - not available - code: { - length: Promise.resolve(0n), - read: async (): Promise => Data.zero(), - }, - - // Transient storage - not available - transient: { - read: async (): Promise => Data.zero(), - }, - }; -} diff --git a/packages/bugc/test/examples/runners.ts b/packages/bugc/test/examples/runners.ts index b8e113f98..91211b4ff 100644 --- a/packages/bugc/test/examples/runners.ts +++ b/packages/bugc/test/examples/runners.ts @@ -10,11 +10,11 @@ import * as Format from "@ethdebug/format"; import { dereference } from "@ethdebug/pointers"; import { bytesToHex } from "ethereum-cryptography/utils"; -import { EvmExecutor } from "../evm/index.js"; +import { Executor } from "@ethdebug/evm"; +import { createMachineState } from "@ethdebug/evm"; import type { VariablesTest } from "./annotations.js"; import type { SourceMapping } from "./source-map.js"; import { findInstructionsAtLine } from "./source-map.js"; -import { createMachineState } from "./machine-adapter.js"; export interface TestResult { passed: boolean; @@ -140,7 +140,7 @@ async function checkScalarValue( after: "deploy" | "call" = "deploy", callData?: string, ): Promise { - const executor = new EvmExecutor(); + const executor = new Executor(); try { // Deploy @@ -212,7 +212,7 @@ async function checkRegionValues( after: "deploy" | "call" = "deploy", callData?: string, ): Promise { - const executor = new EvmExecutor(); + const executor = new Executor(); try { // Deploy diff --git a/packages/bugc/tsconfig.json b/packages/bugc/tsconfig.json index 007376700..845e84acc 100644 --- a/packages/bugc/tsconfig.json +++ b/packages/bugc/tsconfig.json @@ -61,5 +61,9 @@ }, "include": ["src/**/*", "bin/**/*", "test/**/*"], "exclude": ["node_modules", "dist"], - "references": [{ "path": "../format" }, { "path": "../pointers" }] + "references": [ + { "path": "../format" }, + { "path": "../pointers" }, + { "path": "../evm" } + ] } diff --git a/yarn.lock b/yarn.lock index e2fccc278..9bc3a89a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3512,18 +3512,6 @@ ethereum-cryptography "^3.2.0" lru-cache "11.0.2" -"@ethereumjs/block@^10.1.0": - version "10.1.0" - resolved "https://registry.yarnpkg.com/@ethereumjs/block/-/block-10.1.0.tgz#18c19008881a910bd22ce7e519d7a5d5c4a4cee1" - integrity sha512-W2GR/ejYn/idfX4fxQ2DRbrbOF5U04Q2wDiMuxtvnOr8zdVCBSlVYCC348N73ufkrqY5ltTlEUZYWLWWobcS/Q== - dependencies: - "@ethereumjs/common" "^10.1.0" - "@ethereumjs/mpt" "^10.1.0" - "@ethereumjs/rlp" "^10.1.0" - "@ethereumjs/tx" "^10.1.0" - "@ethereumjs/util" "^10.1.0" - ethereum-cryptography "^3.2.0" - "@ethereumjs/common@^10.0.0", "@ethereumjs/common@^10.1.0": version "10.1.0" resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-10.1.0.tgz#f80e5589feae32ba33a818c50b18a361d8bde527" @@ -3532,7 +3520,7 @@ "@ethereumjs/util" "^10.1.0" eventemitter3 "^5.0.1" -"@ethereumjs/evm@^10.0.0", "@ethereumjs/evm@^10.1.0": +"@ethereumjs/evm@^10.0.0": version "10.1.0" resolved "https://registry.yarnpkg.com/@ethereumjs/evm/-/evm-10.1.0.tgz#97ae3a688efd860d7a3bd2251c51ef00ed750d3e" integrity sha512-RD6tjysXEWPfyBHmMxsg3s3T2JdbPVYvsDDDUR0laFaHWtHMnOeW/EIZ5njUx75nG+i72w34Sh6NhhkpkhMGDg== @@ -3578,16 +3566,6 @@ ethereum-cryptography "^3.2.0" lru-cache "11.0.2" -"@ethereumjs/tx@^10.1.0": - version "10.1.0" - resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-10.1.0.tgz#90b568f7a1a8c02a20b2531dbc0375be38dff573" - integrity sha512-svG6pyzUZDpunafszf2BaolA6Izuvo8ZTIETIegpKxAXYudV1hmzPQDdSI+d8nHCFyQfEFbQ6tfUq95lNArmmg== - dependencies: - "@ethereumjs/common" "^10.1.0" - "@ethereumjs/rlp" "^10.1.0" - "@ethereumjs/util" "^10.1.0" - ethereum-cryptography "^3.2.0" - "@ethereumjs/util@^10.0.0", "@ethereumjs/util@^10.1.0": version "10.1.0" resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-10.1.0.tgz#56ba2abd5ca0030a1bb6d543bf205c27307cd592" @@ -3596,23 +3574,6 @@ "@ethereumjs/rlp" "^10.1.0" ethereum-cryptography "^3.2.0" -"@ethereumjs/vm@^10.0.0": - version "10.1.0" - resolved "https://registry.yarnpkg.com/@ethereumjs/vm/-/vm-10.1.0.tgz#96a859946e8d85cd2759ea4b24d9c194d0a5d704" - integrity sha512-5OxHK4hdccDeSiBeSZj8WITl9uB6NbXA6/yf2wAPoflVxZKzOM5h6ssFrOAhz2faD130UoU62pnT23WClgrXSQ== - dependencies: - "@ethereumjs/block" "^10.1.0" - "@ethereumjs/common" "^10.1.0" - "@ethereumjs/evm" "^10.1.0" - "@ethereumjs/mpt" "^10.1.0" - "@ethereumjs/rlp" "^10.1.0" - "@ethereumjs/statemanager" "^10.1.0" - "@ethereumjs/tx" "^10.1.0" - "@ethereumjs/util" "^10.1.0" - debug "^4.4.0" - ethereum-cryptography "^3.2.0" - eventemitter3 "^5.0.1" - "@fortawesome/fontawesome-common-types@6.7.2": version "6.7.2" resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz#7123d74b0c1e726794aed1184795dbce12186470"