Snapshot/commit semantics for nested EVM call frames.
When a child frame halts normally (:stopped), its mutations to the
parent's revertible fields are promoted; on any other halt they are
discarded and the parent observes the pre-call state.
Revertible fields (the db plus all of substate) participate in the
merge:
db— the unified world database (accounts + storage)substate.logs— emitted log entries, newest first (child's ahead of parent's)substate.accessed_addresses— EIP-2929 access listsubstate.accessed_storage_keys— EIP-2929 access listsubstate.created_addresses— EIP-6780 set of contracts created in this txsubstate.selfdestructed_addresses— accounts to delete when the tx endssubstate.touched_addresses— EIP-161 cleanup setframe.refund— the gas refund counter. A child starts at 0, so on success its (possibly negative) counter is added to the parent's; on failure it is dropped.
The tracer and substate.program_cache are not revertible — trace
events and JUMPDEST analyses accumulate monotonically and are always
preserved across child boundaries regardless of the child's outcome.
What this module does NOT cover
- Stack and memory: those are local to a frame and never propagate child → parent on success or failure.
- Gas accounting: returning the child's unused
child.gasto the parent happens at the call site, not here, because the math depends on opcode-specific bookkeeping (e.g. CREATE's deposit cost). - The
:dboverride in CREATE: the success path replaceschild.dbwith a post-deploy db that has the runtime code installed. Call sites apply that override aftermerge_child_result/2.
Summary
Functions
Merge the result of a child frame back into its parent.
Functions
@spec merge_child_result( EEVM.Interpreter.MachineState.t(), EEVM.Interpreter.MachineState.t() ) :: EEVM.Interpreter.MachineState.t()
Merge the result of a child frame back into its parent.
- On success (
status: :stopped): the child's mutations to revertible fields are promoted into the parent. Logs are kept newest first, so the child's logs are prepended to the parent's in O(child) time, and the child's refund counter is added to the parent's. - On any other halt: the parent's revertible fields are kept untouched.
In both cases the child's tracer state is propagated into the parent, because tracing accumulates regardless of execution outcome.