Repository navigation
evm: run multi-account scenarios with per-step state - #375
Merged
Merged
Conversation
Contributor
|
gnidan
added a commit
to ethdebug/argot-post-2026-10
that referenced
this pull request
Oct 9, 2026
…and pointers from the same commit)
A trace step is now only the event (what ran and where). The state at a step comes from a recorded trace, `createTrace().stateAt(index)`, complete and from that step only; `createMachineState` adapts such a state and no longer reads the executor's current state. Also fixes CREATE2 frames under tracing, `gasCost` (the static fee was counted twice), original storage values across transactions, BLOCKHASH in a numbered block, checkpoints left open by a run that throws, and handler errors (now the run's rejection).
…policy A call or create that fails before its message starts now clears the step state's return data, as the EVM does. A trace with a non-literal memory policy gives states that may lack memory, which createMachineState rejects until narrowed. A trace's step state carries its pc, opcode and index, so createMachineState(trace.stateAt(i)) is right without options.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
@ethdebug/evmcould run one contract, deployed from one fixed account, in a block whose fields are all zero, and its machine state mixed a trace step's stack and memory with the executor's current storage. That is not enough to replay a story across several accounts and transactions, or to show the state at an earlier point of a trace. This adds what that needs.Transactions
deploy({ from, create, value })returns the new contract's address;call({ from, to, input, value })calls any address;fund(address, balance)sets a balance.getStorage,setStorageandgetCodetake an optional address (default: the contract deployed last, as before). The legacydeploy(bytecode)andexecute(options, handler)still work.blockoption per call (number, timestamp, prevrandao, coinbase, base fee, blob base fee, gas limit) and achainIdexecutor option. BLOCKHASH of a recent block is keccak256 of its number.endTransaction()clears transient storage, the warm address/slot sets and the slots' original values (EIP-2200), as at a transaction boundary.deploy(options)andcallend their transaction;executedoes not, so existing callers see the same gas. Gas still differs from a chain's (no intrinsic gas, no pre-warmed addresses).Steps and state
TraceStepis the event only:pc,opcode,op,gasRemaining,gasCost,depth,address,codeAddress.createTrace({ memory })records one transaction.trace.stateAt(i)gives the completeStepStateat stepi: stack, memory, storage and transient readers, calldata, return data and code, all from that step. Storage written in a frame that later reverts is undone from the revert on. Storage the transaction never touched is read from the executor only while it is unchanged; after that, the read throws instead of mixing in the present.memory: "full"(default) copies memory at every step,"changed"copies only when it changed,"none"records none, and thenstateAthas nomemory, so passing it tocreateMachineStateis a type error.createMachineState(state, { pc, opcode, traceIndex })adapts a complete state and nothing else.executor.currentState()gives the present, for callers that want it.framehandler getsenter/exitevents for every call and create (depth, addresses, caller, calldata, value, return data, reverted, and the first/last step index);trace.frameskeeps them.Breaking:
TraceSteplosesstack/memory,createMachineStateno longer takes an executor, andcreateTraceCollectoris gone. The in-repo callers (bugc's tests and helpers, the web's trace drawer) are updated here.@ethdebug/programs-reacthas its ownTraceStep(hex strings, per-step storage); a follow-up will align it with this one.Browser: no
Bufferis left, so the package runs without a polyfill. Bundled for the browser (esbuild, minified), it is 314.8 KB / 99.7 KB gzip, up from 309.3 KB / 97.5 KB, and consumers no longer need the 8.7 KB gzip polyfill. A recorded trace withstateAtruns in headless Chromium with noBufferdefined.The tests cover calls between two contracts from three accounts, block fields and BLOCKHASH, exact gas costs, transient storage, cold slots and original values across transactions,
stateAtacross nested frames and a reverted middle frame, DELEGATECALL, CREATE and CREATE2 frames, the memory policies over every memory-writing instruction, handler errors, recovery from a run that throws, and a determinism digest.