Repository navigation
program/context/function/invoke: external-call examples contradict the postcondition rule #302
Description
#281 settled that an instruction's context is a postcondition, pointers included: ethdebug/format/program/instruction now says that a context's facts and "any pointers it contains resolve against the machine state after the instruction has executed", and that a debugger paused about to execute instruction i reads the context of instruction i − 1.
ethdebug/format/program/context/function/invoke still says the opposite, and its external-call examples depend on the opposite.
- Its description keeps the sentence that format: state the postcondition convention for instruction contexts #281 removed from the instruction schema: "Pointers within the context reference the machine state at the instruction's trace step, which is the state a debugger observes when it encounters the instruction." The spec page (
packages/web/spec/program/context/function/invoke.mdx, "Pointer evaluation and instruction placement") has the same text. - The external CALL, DELEGATECALL and CREATE2 examples place the
invokecontext on the call instruction itself, with pointers to stack slots 0–2 for gas, target and value. Their comments say so directly: "Stack-based pointers reference the pre-execution state visible in the trace step (CALL consumes all stack operands)." - The external-call example in ethdebug/format/program/context/function/return has the matching pre-state reading: it "would mark an instruction after a CALL", with the success flag in slot 0.
Concretely:
offset op stack after it executes (top first)
90 PUSH.. [gas, target, value, inOff, inLen, outOff, outLen]
91 CALL [success]
An invoke on instruction 91 whose gas pointer is stack slot 0 is right before CALL executes and wrong after it: under the postcondition rule slot 0 holds the success flag.
Internal calls are not affected. The invoke sits on the callee's entry JUMPDEST, which leaves the stack alone, so both readings agree. The reference implementation is not affected either: bugc emits no message or create invokes. This is a problem in the schema text, the examples and the spec page only.
Options
- Move the placement and keep one rule. An external-call or create
invokegoes on the instruction immediately before the CALL/CREATE. Its postcondition is the call's precondition, so the operands are on the stack, the pointer slots in the current examples stay correct as written, and a debugger paused about to execute the CALL reads exactly that context. An externalreturnorrevertgoes on the CALL itself, whose post-state has the success flag in slot 0 and the returndata set. Only descriptions and example comments change, in invoke, return, revert and the spec pages. - Keep the placement and state an exception. The context stays on the CALL/CREATE, and the spec says that pointers in a
messageorcreateinvoke resolve against the state before the marked instruction executes. The examples stay as they are; the postcondition rule is no longer uniform, and a debugger has to special-case these contexts.
Either way the leftover "trace step" sentence has to go.
Found while auditing the spec changelog in #300.
Activity
gnidan commentedon Sep 17, 2026
Settled: the invoke stays on the CALL/CREATE, since the invocation does occur following that instruction's execution. It just so happens that its pointer fields point back:
target,gas,value,inputandsaltdescribe the operands the instruction consumes, so they resolve against the state immediately before it executes. Same shape as DWARF call-site parameters, which are defined at the time of the call. Moving the context to the preceding instruction would make the pointers right but the fact wrong for a step, and would be fragile under operand reordering. Neither solc nor bugc emitsmessage/createinvokes yet, and soldb reads onlyidentifier,declarationandactivation, so nobody has to change. Wording fix in #303.