Skip to content
Open
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/evm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
(modification: no type change headlines) and this project adheres to
[Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## Unreleased

- Add optional `pushData` on `InterpreterStep` for `PUSH0`–`PUSH32` opcode traces, see Issue [#2418](https://github.com/ethereumjs/ethereumjs-monorepo/issues/2418)

## 10.1.1 - 2025-01-28

- Ensure `codeAddress` in step event is correctly set, see PR [#4189](https://github.com/ethereumjs/ethereumjs-monorepo/pull/4189)
Expand Down
2 changes: 1 addition & 1 deletion packages/evm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ You can subscribe to the following events:

- `beforeMessage`: Emits a `Message` right after running it.
- `afterMessage`: Emits an `EVMResult` right after running a message.
- `step`: Emits an `InterpreterStep` right before running an EVM step.
- `step`: Emits an `InterpreterStep` right before running an EVM step. For `PUSH0`–`PUSH32`, `InterpreterStep.pushData` contains the bytes placed on the stack (one `0x00` byte for `PUSH0`, otherwise the immediate bytes from bytecode).
- `newContract`: Emits a `NewContractEvent` right before creating a contract. This event contains the deployment code, not the deployed code, as the creation message may not return such a code.

#### Event listeners
Expand Down
17 changes: 17 additions & 0 deletions packages/evm/src/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ export interface InterpreterStep {
codeAddress: Address
eofSection?: number // Current EOF section being executed
immediate?: Uint8Array // Immediate argument of the opcode
/**
* Bytes placed on the stack by `PUSH0`–`PUSH32` (present only on those steps).
* For `PUSH0` this is a single `0x00` byte; for `PUSHn` it is the `n` immediate bytes from code.
*/
pushData?: Uint8Array
eofFunctionDepth?: number // Depth of CALLF return stack
error?: Uint8Array // Error bytes returned if revert occurs
storage?: [PrefixedHexString, PrefixedHexString][]
Expand Down Expand Up @@ -489,6 +494,16 @@ export class Interpreter {
)
}

let pushData: Uint8Array | undefined
const opCodeNum = opcodeInfo.code
if (opCodeNum >= 0x5f && opCodeNum <= 0x7f) {
if (opCodeNum === 0x5f) {
pushData = new Uint8Array([0])
} else {
pushData = immediate
}
}

// Create event object for step
const eventObj: InterpreterStep = {
pc: this._runState.programCounter,
Expand All @@ -511,6 +526,7 @@ export class Interpreter {
stateManager: this._runState.stateManager,
eofSection: section,
immediate,
pushData,
error,
eofFunctionDepth:
this._env.eof !== undefined ? this._env.eof?.eofRunState.returnStack.length + 1 : undefined,
Expand Down Expand Up @@ -565,6 +581,7 @@ export class Interpreter {
* @property {Address} codeAddress the address of the code which is currently being ran (this differs from `address` in a `DELEGATECALL` and `CALLCODE` call)
* @property {number} eofSection the current EOF code section referenced by the PC
* @property {Uint8Array} immediate the immediate argument of the opcode
* @property {Uint8Array} pushData bytes pushed by PUSH0–PUSH32 (only present for those opcodes)
* @property {Uint8Array} error the error data of the opcode (only present for REVERT)
* @property {number} eofFunctionDepth the depth of the function call (only present for EOF)
* @property {Array} storage an array of tuples, where each tuple contains a storage key and value
Expand Down
56 changes: 56 additions & 0 deletions packages/evm/test/stepPushData.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { equalsBytes, hexToBytes } from '@ethereumjs/util'
import { assert, describe, it } from 'vitest'

import { createEVM } from '../src/index.ts'

import type { InterpreterStep } from '../src/index.ts'

describe('step event pushData', () => {
it('sets pushData for PUSH0', async () => {
const evm = await createEVM()
const steps: InterpreterStep[] = []
evm.events.on('step', (e) => {
steps.push(e)
})
await evm.runCode({ code: hexToBytes('0x5f00'), gasLimit: 100000n })
const pushStep = steps.find((s) => s.opcode.code === 0x5f)
assert.isDefined(pushStep?.pushData)
assert.isTrue(equalsBytes(pushStep!.pushData!, new Uint8Array([0])))
})

it('sets pushData for PUSH1 to the immediate byte(s)', async () => {
const evm = await createEVM()
const steps: InterpreterStep[] = []
evm.events.on('step', (e) => {
steps.push(e)
})
await evm.runCode({ code: hexToBytes('0x60ab00'), gasLimit: 100000n })
const pushStep = steps.find((s) => s.opcode.code === 0x60)
assert.isDefined(pushStep?.pushData)
assert.isTrue(equalsBytes(pushStep!.pushData!, new Uint8Array([0xab])))
})

it('sets pushData length 32 for PUSH32', async () => {
const evm = await createEVM()
const steps: InterpreterStep[] = []
evm.events.on('step', (e) => {
steps.push(e)
})
const imm = '01'.repeat(32)
await evm.runCode({ code: hexToBytes(`0x7f${imm}00`), gasLimit: 100000n })
const pushStep = steps.find((s) => s.opcode.code === 0x7f)
assert.isDefined(pushStep?.pushData)
assert.strictEqual(pushStep!.pushData!.length, 32)
})

it('does not set pushData for non-PUSH opcodes', async () => {
const evm = await createEVM()
const steps: InterpreterStep[] = []
evm.events.on('step', (e) => {
steps.push(e)
})
await evm.runCode({ code: hexToBytes('0x600160020100'), gasLimit: 100000n })
const addStep = steps.find((s) => s.opcode.code === 0x01)
assert.isUndefined(addStep?.pushData)
})
})
Loading