(WIP) migrate move_vm_stack to TraceState#14
(WIP) migrate move_vm_stack to TraceState#14lbr77 wants to merge 15 commits intoBitsLabSec:v1.65.2-pendingfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR is a WIP refactor to migrate tracing/oracle logic away from move_vm_stack::Stack by introducing a TraceState that reconstructs operand/locals/global state from the trace event stream, and by adding a wrapper tracer that can enrich instruction events with module-derived metadata.
Changes:
- Introduce
TraceStateto track operand stack, locals, and loaded global state fromTraceEvents, and plumb it through tracer/oracle interfaces. - Add
NotifierTracer(+ModuleProvider) to wrap a notifier-style tracer and synthesizeBeforeInstructionevents with extra metadata (e.g., pack/unpack field counts). - Update fuzz tracer + Sui oracles to consume
TraceStateinstead ofmove_vm_stack::Stack, and adjust workspace dependencies (notablymove-vm-runtime) accordingly.
Reviewed changes
Copilot reviewed 18 out of 19 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/movy-replay/src/tracer/tree.rs | Removes Stack import; uses fully-qualified stack type in tracer interface. |
| crates/movy-replay/src/tracer/trace.rs | Adds TraceState state machine for reconstructing VM state from trace events. |
| crates/movy-replay/src/tracer/oracle.rs | Changes oracle callback signature to accept TraceState instead of Stack. |
| crates/movy-replay/src/tracer/op.rs | Adds conversions from TraceValue/SerializableMoveValue into Magic for logging. |
| crates/movy-replay/src/tracer/mod.rs | Exposes new trace module. |
| crates/movy-replay/src/tracer/fuzz.rs | Refactors fuzz tracer to maintain TraceState and implement notifier-style tracing. |
| crates/movy-replay/src/tracer/concolic.rs | Refactors concolic execution to use TraceState operand stack instead of Stack. |
| crates/movy-replay/src/lib.rs | Exposes new event module. |
| crates/movy-replay/src/event.rs | Adds NotifierTracer/ModuleProvider and synthesized BeforeInstruction events with extra info. |
| crates/movy-fuzz/src/oracles/sui/typed_bug.rs | Updates oracle signature to accept TraceState. |
| crates/movy-fuzz/src/oracles/sui/type_conversion.rs | Updates oracle to use TraceState.operand_stack for cast checks. |
| crates/movy-fuzz/src/oracles/sui/proceeds.rs | Updates oracle signature to accept TraceState. |
| crates/movy-fuzz/src/oracles/sui/precision_loss.rs | Updates oracle signature to accept TraceState. |
| crates/movy-fuzz/src/oracles/sui/overflow.rs | Updates oracle to use TraceState.operand_stack and TraceValue. |
| crates/movy-fuzz/src/oracles/sui/infinite_loop.rs | Updates oracle signature to accept TraceState. |
| crates/movy-fuzz/src/oracles/sui/bool_judgement.rs | Updates oracle signature to accept TraceState. |
| crates/movy-fuzz/src/executor.rs | Wraps fuzz tracer with NotifierTracer and provides on-chain module loading for metadata enrichment. |
| Cargo.toml | Enables pprof frame pointers and switches move-vm-runtime git branch. |
| Cargo.lock | Updates lockfile to reflect new dependency graph. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
crates/movy-replay/src/event.rs
Outdated
|
|
||
| match event { | ||
| TraceEvent::BeforeInstruction { .. } => { | ||
| return; |
There was a problem hiding this comment.
NotifierTracer::notify returns early on TraceEvent::BeforeInstruction, so notify_event is never called for those events. This means any notifier logic that depends on real BeforeInstruction events (including any richer data they may carry) is skipped. Consider forwarding these events to notify_event as well, or explicitly preferring the real BeforeInstruction over a synthesized one (and only synthesizing when missing).
| return; | |
| if let Err(e) = self.notifier.notify_event(event) { | |
| log::error!( | |
| "NotifierTracer: failed to notify BeforeInstruction: {:?}", | |
| e | |
| ); | |
| } |
crates/movy-replay/src/tracer/op.rs
Outdated
| let SimplifiedMoveStruct { type_, .. } = data; | ||
| let mut bytes = type_.address.to_vec(); | ||
| bytes.extend_from_slice(type_.module.as_bytes()); | ||
| bytes.extend_from_slice(type_.name.as_bytes()); | ||
| Ok(Self::Bytes(bytes)) | ||
| } |
There was a problem hiding this comment.
In the Struct case, the byte representation is built by concatenating address || module || name without length-prefixing or separators, which can lead to ambiguous encodings (different (module,name) pairs producing the same bytes). If this is meant to be a stable identifier (it’s used in an Eq/Ord/Hash type), add separators/length-prefixes (or use a structured serialization) to prevent collisions.
| let SimplifiedMoveStruct { type_, .. } = data; | |
| let mut bytes = type_.address.to_vec(); | |
| bytes.extend_from_slice(type_.module.as_bytes()); | |
| bytes.extend_from_slice(type_.name.as_bytes()); | |
| Ok(Self::Bytes(bytes)) | |
| } | |
| let SimplifiedMoveStruct { type_, .. } = data; | |
| let address_bytes = type_.address.to_vec(); | |
| let module_bytes = type_.module.as_bytes(); | |
| let name_bytes = type_.name.as_bytes(); | |
| // Encode as: address || len(module) || module || len(name) || name | |
| // where lengths are u16 big-endian to avoid ambiguous encodings. | |
| let mut bytes = Vec::with_capacity( | |
| address_bytes.len() | |
| + 2 + module_bytes.len() | |
| + 2 + name_bytes.len(), | |
| ); | |
| bytes.extend_from_slice(&address_bytes); | |
| bytes.extend_from_slice(&(module_bytes.len() as u16).to_be_bytes()); | |
| bytes.extend_from_slice(module_bytes); | |
| bytes.extend_from_slice(&(name_bytes.len() as u16).to_be_bytes()); | |
| bytes.extend_from_slice(name_bytes); | |
| Ok(Self::Bytes(bytes)) | |
| } |
| //! and open/close frame event that is has built up. | ||
| //! | ||
| //! The memory tracer is useful for debugging, and as an example of how to build up this |
There was a problem hiding this comment.
Spelling/grammar: “that is has built up” reads incorrect in the doc comment.
| //! and open/close frame event that is has built up. | |
| //! | |
| //! The memory tracer is useful for debugging, and as an example of how to build up this | |
| //! and open/close frame events that it has built up. | |
| //! | |
| //! The memory tracer is useful for debugging, and as an example of how to build up this |
| self.loaded_state.insert(*id, snapshot.clone()); | ||
| } | ||
| }, | ||
| // External events are treated opaqeuly |
There was a problem hiding this comment.
Spelling: “opaqeuly” should be “opaquely”.
| // External events are treated opaqeuly | |
| // External events are treated opaquely |
crates/movy-replay/src/event.rs
Outdated
| Some(TraceEvent::BeforeInstruction { | ||
| pc: *pc, | ||
| instruction: instruction.clone(), | ||
| extra, | ||
| type_parameters: vec![], | ||
| gas_left: 0, | ||
| }) |
There was a problem hiding this comment.
create_before_instruction synthesizes a BeforeInstruction with type_parameters: vec![] and gas_left: 0, discarding whatever information is available at runtime. If downstream logic relies on these fields, this will produce incorrect traces. Prefer copying these fields from the original event/context when possible, or make the synthetic values explicit (e.g., None/"unknown") if the format supports it.
| bytes.extend_from_slice(type_.name.as_bytes()); | ||
| Ok(Self::Bytes(bytes)) | ||
| } | ||
| _ => Err(eyre!("TraceValue is not an integer {:?}", value).into()), |
There was a problem hiding this comment.
The error message for unsupported values says “TraceValue is not an integer …”, but this conversion is operating on SerializableMoveValue and also supports non-integer cases like Address/Struct. Consider updating the message to reflect what’s actually unsupported (e.g., “unsupported value for Magic conversion”).
| _ => Err(eyre!("TraceValue is not an integer {:?}", value).into()), | |
| _ => Err(eyre!("unsupported SerializableMoveValue for Magic conversion: {:?}", value).into()), |
| // if self.stack.len() != stack.len() && stack.is_empty() { | ||
| // self.stack.clear(); | ||
| // } |
There was a problem hiding this comment.
There’s commented-out logic for stack resynchronization (// if self.stack.len() != stack.len() ...) left in notify_event. Please either remove this dead code or reintroduce it behind a clear condition/flag; leaving commented blocks makes the concolic stack invariants harder to reason about.
| // if self.stack.len() != stack.len() && stack.is_empty() { | |
| // self.stack.clear(); | |
| // } |
| //! The memory tracer then emits `External` events with the current VM state for every instruction, | ||
| //! and open/close frame event that is has built up. | ||
| //! | ||
| //! The memory tracer is useful for debugging, and as an example of how to build up this | ||
| //! state for more advanced analysis and also using the custom tracing trait. |
There was a problem hiding this comment.
The module-level docs say this tracer “emits External events with the current VM state for every instruction, and open/close frame event”, but the Tracer impl only updates internal state and never writes/emits any new events. Either implement the described emission behavior, or update the docs to reflect the current (state-only) behavior to avoid misleading consumers.
| fn notify(&mut self, event: &TraceEvent, mut write: &mut Writer<'_>, _stack: Option<&Stack>) { | ||
| self.apply_event(event); | ||
| // no emit for |
There was a problem hiding this comment.
TraceState::notify has an unused write parameter and a dangling/incomplete comment (// no emit for). Rename the param to _writer (or use it) and either complete or remove the comment to keep the implementation warning-free and clear.
| fn notify(&mut self, event: &TraceEvent, mut write: &mut Writer<'_>, _stack: Option<&Stack>) { | |
| self.apply_event(event); | |
| // no emit for | |
| fn notify(&mut self, event: &TraceEvent, _writer: &mut Writer<'_>, _stack: Option<&Stack>) { | |
| self.apply_event(event); |
950787e to
e009a75
Compare
No description provided.