Skip to content

fix(revm): preserve syscall inspector stack snapshots for call-like traces#344

Draft
hedwig0x wants to merge 3 commits into
fluentlabs-xyz:develfrom
hedwig0x:fix/inspector-syscall-stack-snapshots
Draft

fix(revm): preserve syscall inspector stack snapshots for call-like traces#344
hedwig0x wants to merge 3 commits into
fluentlabs-xyz:develfrom
hedwig0x:fix/inspector-syscall-stack-snapshots

Conversation

@hedwig0x
Copy link
Copy Markdown
Collaborator

@hedwig0x hedwig0x commented Mar 13, 2026

Summary

When syscall interruptions are inspected in crates/revm/src/syscall.rs, we were only forwarding synthetic input stack values to inspect_syscall, and never forwarding synthetic output stack values.

For call-like opcodes (CALL*/CREATE*), this makes inspector step/step_end snapshots inconsistent and can cause missing/incomplete nested call metadata in tracers that derive details from stack snapshots.

Root cause

  • inspect! macro accepted ($inputs, $outputs) but only passed $inputs.
  • inspect_syscall did not preserve/restore the interpreter stack around synthetic inspection.

Fix

  • Extend inspect_syscall to accept both input and output iterators.
  • Save/restore interpreter bytecode and stack around synthetic inspection.
  • Feed input stack before inspector.step, and output stack before inspector.step_end.
  • Update all call sites (syscall.rs, executor.rs).

Test

Added a unit test in crates/revm/src/inspector.rs that verifies:

  • DELEGATECALL synthetic opcode is observed by inspector step hook
  • input stack ordering is preserved (top-of-stack semantics)
  • output stack is visible in step_end
  • original interpreter stack is restored after inspection

Notes

I couldn't run Rust tests in this environment because cargo is unavailable in PATH, but the changes are localized and covered by the new unit test.

Summary by CodeRabbit

  • Bug Fixes

    • Improved system call inspection to prevent unintended state mutations during debugging operations.
  • Refactor

    • Enhanced inspection mechanism to capture and validate input/output parameters more accurately.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 13, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e36d8b27-8864-4e5b-ae05-dd78b13bbe58

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The pull request updates the inspect_syscall function to accept an additional output parameter representing the syscall's output stack, enabling synthetic inspection of syscall behavior by temporarily creating a synthetic interpreter state with provided inputs and outputs, then restoring the original frame state.

Changes

Cohort / File(s) Summary
Call Site Update
crates/revm/src/executor.rs
Updated inspect_syscall invocation in process_halt to pass an additional empty slice argument for outputs parameter.
Synthetic Inspection Implementation
crates/revm/src/inspector.rs
Added OUT generic parameter to inspect_syscall with DoubleEndedIterator constraints; implemented synthetic state creation (save/restore frame, setup synthetic bytecode and stack with inputs/outputs), inspector callbacks, and test module validating state isolation.
Macro Integration
crates/revm/src/syscall.rs
Updated inspect macro to forward an additional $outputs argument to inspect_syscall invocation.

Sequence Diagram

sequenceDiagram
    participant Executor
    participant InspectSyscall
    participant Frame
    participant Inspector

    Executor->>InspectSyscall: inspect_syscall(frame, inputs, outputs)
    InspectSyscall->>Frame: Save current bytecode & stack
    InspectSyscall->>Frame: Set synthetic bytecode for opcode
    InspectSyscall->>Frame: Initialize empty stack
    InspectSyscall->>Frame: Push input arguments (reverse order)
    InspectSyscall->>Inspector: step(synthetic_state)
    InspectSyscall->>Frame: Create post-op stack from outputs
    InspectSyscall->>Inspector: step_end(synthetic_state)
    InspectSyscall->>Frame: Restore original bytecode & stack
    InspectSyscall->>Executor: Return (frame unchanged)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Suggested reviewers

  • dmitry123

Poem

🐰 Whisker-twitching code review delight!
A synthetic dance in the inspector's light,
States saved and restored with graceful care,
Input and output stacks floating through air!
The frame emerges unchanged, pristine and bright! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately describes the main change: enabling the inspector to capture both input and output stack snapshots for syscalls to fix tracing of call-like opcodes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@hedwig0x hedwig0x force-pushed the fix/inspector-syscall-stack-snapshots branch from cd8fcb0 to b2bd5c9 Compare March 13, 2026 11:34
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/revm/src/inspector.rs (1)

86-118: Assert bytecode restoration too.

This regression only checks that the original stack comes back. The production change also swaps frame.interpreter.bytecode, so a bytecode-restore regression would still pass here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/revm/src/inspector.rs` around lines 86 - 118, The test
inspect_syscall_restores_interpreter_state_and_preserves_io_stacks must also
assert that frame.interpreter.bytecode is restored after inspect_syscall: set a
distinct bytecode into frame.interpreter.bytecode (or record the existing
bytecode clone) before calling inspect_syscall, then after the call add an
assertion that the post-call frame.interpreter.bytecode equals the
saved/original bytecode; reference the test function name and the
inspect_syscall entry point and the frame.interpreter.bytecode field to locate
where to add the save-and-assert.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/revm/src/inspector.rs`:
- Around line 43-50: The inspector currently fires inspector.step_end(&mut
frame.interpreter, ctx) immediately in inspect_syscall after synthesizing
frame.interpreter.stack from output, but syscall paths in
crates/revm/src/syscall.rs still call this helper with output == [] for
CALL/STATICCALL/CALLCODE/DELEGATECALL/CREATE/CREATE2 so tracers never see the
child-frame return word/address; change the flow to either defer calling
inspector.step_end until the child frame has settled (i.e., move the step_end
invocation to the point in syscall handling where the child frame’s
result/created address is known) or modify the helper signature so the eventual
return word/address is plumbed back into inspect_syscall (and used to populate
output) before calling inspector.step_end; locate and update inspect_syscall and
the syscall handlers in syscall.rs to ensure the synthesized
frame.interpreter.stack contains the real return value when step_end is invoked.

---

Nitpick comments:
In `@crates/revm/src/inspector.rs`:
- Around line 86-118: The test
inspect_syscall_restores_interpreter_state_and_preserves_io_stacks must also
assert that frame.interpreter.bytecode is restored after inspect_syscall: set a
distinct bytecode into frame.interpreter.bytecode (or record the existing
bytecode clone) before calling inspect_syscall, then after the call add an
assertion that the post-call frame.interpreter.bytecode equals the
saved/original bytecode; reference the test function name and the
inspect_syscall entry point and the frame.interpreter.bytecode field to locate
where to add the save-and-assert.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d38eac6c-a805-4ead-84a4-11486c2bc6a5

📥 Commits

Reviewing files that changed from the base of the PR and between dd74460 and b2bd5c9.

📒 Files selected for processing (3)
  • crates/revm/src/executor.rs
  • crates/revm/src/inspector.rs
  • crates/revm/src/syscall.rs

Comment on lines +43 to 50
// Provide post-op stack shape for inspectors that persist `step_end` stack snapshots.
frame.interpreter.stack = Stack::new();
for x in output.into_iter().rev() {
_ = frame.interpreter.stack.push(x);
}

// TODO: For CALL*/CREATE* opcodes this should ideally run once the child frame settles.
inspector.step_end(&mut frame.interpreter, ctx);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

CALL/CREATE still end with an empty synthetic post-op stack.**

inspect_syscall always fires step_end immediately, but the real CALL/STATICCALL/CALLCODE/DELEGATECALL/CREATE/CREATE2 paths in crates/revm/src/syscall.rs still invoke this helper with [] for output. Tracers that read step_end therefore still never see the success word / created address for the exact call-like opcodes this PR is trying to fix. Please defer step_end until the child frame settles, or plumb the eventual return word/address back into this helper before calling it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/revm/src/inspector.rs` around lines 43 - 50, The inspector currently
fires inspector.step_end(&mut frame.interpreter, ctx) immediately in
inspect_syscall after synthesizing frame.interpreter.stack from output, but
syscall paths in crates/revm/src/syscall.rs still call this helper with output
== [] for CALL/STATICCALL/CALLCODE/DELEGATECALL/CREATE/CREATE2 so tracers never
see the child-frame return word/address; change the flow to either defer calling
inspector.step_end until the child frame has settled (i.e., move the step_end
invocation to the point in syscall handling where the child frame’s
result/created address is known) or modify the helper signature so the eventual
return word/address is plumbed back into inspect_syscall (and used to populate
output) before calling inspector.step_end; locate and update inspect_syscall and
the syscall handlers in syscall.rs to ensure the synthesized
frame.interpreter.stack contains the real return value when step_end is invoked.

@dmitry123 dmitry123 marked this pull request as draft March 15, 2026 08:03
@hedwig0x hedwig0x force-pushed the fix/inspector-syscall-stack-snapshots branch 2 times, most recently from 78572b1 to a834a3e Compare March 31, 2026 14:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant