From d6e55ea324f420d962050c38023b0342ee2273a7 Mon Sep 17 00:00:00 2001 From: Jacques-codes Date: Thu, 28 May 2026 04:10:48 +0200 Subject: [PATCH] feat: add storage mutation markers to dynamic traces --- src/cli/commands.rs | 21 +++++++++++++++++++++ src/inspector/storage.rs | 24 ++++++++++++++++++++++++ src/runtime/executor.rs | 8 ++++++-- 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/cli/commands.rs b/src/cli/commands.rs index cbee9755..82e1f5bc 100644 --- a/src/cli/commands.rs +++ b/src/cli/commands.rs @@ -1058,11 +1058,22 @@ pub fn run(args: RunArgs, verbosity: Verbosity) -> Result<()> { let mut pauses = Vec::new(); let hit_entry_breakpoint = args.breakpoint.iter().any(|bp| bp == function); if engine.is_paused() && hit_entry_breakpoint { + let storage_mutation = if storage_diff.is_empty() { + None + } else { + let mutated_keys = storage_diff.mutated_keys(); + Some(crate::debugger::timeline::StorageMutationMarker { + affected_key_count: mutated_keys.len(), + mutated_keys, + }) + }; + pauses.push(TimelinePausePoint { index: 0, reason: "breakpoint".to_string(), location: None, call_stack: stack_summary.clone(), + storage_mutation, }); } @@ -3149,4 +3160,14 @@ mod tests { } } // +/////// + assert!(json.get("binary").is_some()); + assert!(json.get("config").is_some()); + assert!(json.get("history").is_some()); + assert!(json.get("plugins").is_some()); + assert!(json.get("protocol").is_some()); + assert!(json.get("vscode_extension").is_some()); + } +} +// /////// diff --git a/src/inspector/storage.rs b/src/inspector/storage.rs index 6f5b86bd..45e183ea 100644 --- a/src/inspector/storage.rs +++ b/src/inspector/storage.rs @@ -401,6 +401,13 @@ impl StorageInspector { *self.writes.entry(key.to_string()).or_insert(0) += 1; } + /// Get a sorted list of all keys that have been written to + pub fn mutated_keys(&self) -> Vec { + let mut keys: Vec = self.writes.keys().cloned().collect(); + keys.sort(); + keys + } + /// Analyze access patterns pub fn analyze_access_patterns(&self) -> AccessPatternReport { let mut stats: HashMap = HashMap::new(); @@ -706,6 +713,17 @@ impl StorageDiff { pub fn is_empty(&self) -> bool { self.added.is_empty() && self.modified.is_empty() && self.deleted.is_empty() } + + /// Extract a sorted list of all keys that were mutated (added, modified, or deleted) + pub fn mutated_keys(&self) -> Vec { + let mut keys: Vec = self.added.keys().cloned() + .chain(self.modified.keys().cloned()) + .chain(self.deleted.iter().cloned()) + .collect(); + keys.sort(); + keys.dedup(); + keys + } } /// Statistics for a single storage access key @@ -1152,6 +1170,12 @@ mod tests { &("old".to_string(), "new".to_string()) ); assert!(diff.deleted.contains(&"key_76".to_string())); + + let mutated = diff.mutated_keys(); + assert_eq!(mutated.len(), 75); + assert!(mutated.contains(&"key_101".to_string())); + assert!(mutated.contains(&"key_51".to_string())); + assert!(mutated.contains(&"key_76".to_string())); } #[test] diff --git a/src/runtime/executor.rs b/src/runtime/executor.rs index b8d72c73..5ee40876 100644 --- a/src/runtime/executor.rs +++ b/src/runtime/executor.rs @@ -12,6 +12,7 @@ use crate::inspector::budget::MemorySummary; use crate::output::InvocationReason; use crate::runtime::env::DebugEnv; use crate::runtime::mocking::{MockCallLogEntry, MockContractDispatcher, MockRegistry}; +use crate::debugger::timeline::StorageMutationMarker; use crate::server::protocol::{DynamicTraceEvent, DynamicTraceEventKind}; use crate::utils::arguments::ArgumentParser; use crate::{DebuggerError, Result}; @@ -152,7 +153,7 @@ impl ContractExecutor { // Track storage changes as accesses let storage_after = &record.storage_after; - self.track_storage_changes(&storage_before, storage_after); + let _mutation_marker = self.track_storage_changes(&storage_before, storage_after); // Record completed function call let result_str = display.clone(); @@ -178,15 +179,18 @@ impl ContractExecutor { &mut self, storage_before: &HashMap, storage_after: &HashMap, - ) { + ) -> Option { + let mut mutated_keys = Vec::new(); // Track writes (new or modified entries) for (key, value) in storage_after { if !storage_before.contains_key(key) { // New write self.debug_env.track_storage_write(key, value); + mutated_keys.push(key.clone()); } else if storage_before.get(key) != Some(value) { // Modified write self.debug_env.track_storage_write(key, value); + mutated_keys.push(key.clone()); } }