diff --git a/src/cli/commands.rs b/src/cli/commands.rs index f4a456b5..f8b4714b 100644 --- a/src/cli/commands.rs +++ b/src/cli/commands.rs @@ -1247,6 +1247,7 @@ pub fn run(args: RunArgs, verbosity: Verbosity) -> Result<()> { reason: "breakpoint".to_string(), location: None, call_stack: stack_summary.clone(), + storage_mutation, }); } @@ -3463,4 +3464,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 40323d43..bf959ce9 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()); } }