From 142d9b5595b1150c0361fe32bb6776193779d4c2 Mon Sep 17 00:00:00 2001 From: developeruche Date: Thu, 14 Aug 2025 15:48:42 +0100 Subject: [PATCH 1/9] done introducing the call frame iter= --- crates/rpc-types-trace/src/geth/call.rs | 114 ++++++++++++++++++ .../call_tracer/multi_call_default.json | 112 +++++++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 crates/rpc-types-trace/test_data/call_tracer/multi_call_default.json diff --git a/crates/rpc-types-trace/src/geth/call.rs b/crates/rpc-types-trace/src/geth/call.rs index c29ed69a1e8..2bfbdd4f9f0 100644 --- a/crates/rpc-types-trace/src/geth/call.rs +++ b/crates/rpc-types-trace/src/geth/call.rs @@ -172,6 +172,56 @@ impl FlatCallConfig { } } +/// An iterator for traversing `CallFrame` hierarchies. +/// +/// Traversal is **depth-first** by default. +/// You can skip children of the current frame via [`skip_children`]. +#[derive(Debug)] +pub struct CallFrameIter<'a> { + /// Stack of (frame reference, parent reference) + stack: Vec<(&'a CallFrame, Option<&'a CallFrame>)>, + skip_depth: Option, +} + +impl<'a> CallFrameIter<'a> { + /// Creates a new iterator starting from `root`. + pub fn new(root: &'a CallFrame) -> Self { + Self { stack: vec![(root, None)], skip_depth: None } + } + + /// Skip all children of the **current** frame. + /// + /// This applies only to the *next* [`next`] call, + /// and will prevent traversal into its children. + pub fn skip_children(&mut self) { + // Mark the current depth so we can avoid pushing its children + self.skip_depth = Some(self.stack.len()); + } +} + +impl<'a> Iterator for CallFrameIter<'a> { + type Item = (&'a CallFrame, Option<&'a CallFrame>); + + fn next(&mut self) -> Option { + let (frame, parent) = self.stack.pop()?; + + // Determine if we should skip children for this frame + let should_skip = + self.skip_depth.map(|depth| depth == self.stack.len() + 1).unwrap_or(false); + + if should_skip { + self.skip_depth = None; // reset skip state + } else { + // Push children in reverse so iteration order matches original vector + for child in frame.calls.iter().rev() { + self.stack.push((child, Some(frame))); + } + } + + Some((frame, parent)) + } +} + #[cfg(test)] mod tests { use super::*; @@ -180,6 +230,7 @@ mod tests { // See const DEFAULT: &str = include_str!("../../test_data/call_tracer/default.json"); + const MULTI_DEFAULT: &str = include_str!("../../test_data/call_tracer/multi_call_default.json"); const LEGACY: &str = include_str!("../../test_data/call_tracer/legacy.json"); const ONLY_TOP_CALL: &str = include_str!("../../test_data/call_tracer/only_top_call.json"); const WITH_LOG: &str = include_str!("../../test_data/call_tracer/with_log.json"); @@ -208,4 +259,67 @@ mod tests { let _trace: CallFrame = serde_json::from_str(ONLY_TOP_CALL).unwrap(); let _trace: CallFrame = serde_json::from_str(WITH_LOG).unwrap(); } + + #[test] + fn test_call_frame_iter() { + let mut init_frame: CallFrame = serde_json::from_str(DEFAULT).unwrap(); + init_frame.calls.push(init_frame.clone()); + + let mut call_iter = CallFrameIter::new(&init_frame); + + let call_1 = call_iter.next().unwrap(); + assert_eq!(call_1.0.calls.len(), 2); + assert_eq!(*call_1.0, init_frame); + + let call_2 = call_iter.next().unwrap(); + assert_eq!(call_2.0.calls.len(), 0); + let init_frame_raw: CallFrame = serde_json::from_str(DEFAULT).unwrap(); + assert_eq!(*call_2.0, init_frame_raw.calls[0]); + + let call_3 = call_iter.next().unwrap(); + assert_eq!(call_3.0.calls.len(), 1); + assert_eq!(*call_3.0, init_frame_raw); + + let call_4 = call_iter.next().unwrap(); + assert_eq!(call_4.0.calls.len(), 0); + assert_eq!(*call_4.0, init_frame_raw.calls[0]); + } + + #[test] + fn test_call_frame_iter_with_skip_child_1() { + let mut init_frame: CallFrame = serde_json::from_str(DEFAULT).unwrap(); + init_frame.calls.push(init_frame.clone()); + let init_frame_raw: CallFrame = serde_json::from_str(DEFAULT).unwrap(); + init_frame.calls[0].calls.push(init_frame_raw.clone()); + + let mut call_iter = CallFrameIter::new(&init_frame); + + call_iter.skip_children(); + + let _call_1 = call_iter.next().unwrap(); + let call_2 = call_iter.next(); + + assert_eq!(call_2, None); + } + + #[test] + fn test_call_frame_iter_with_skip_child_2() { + let init_frame: CallFrame = serde_json::from_str(MULTI_DEFAULT).unwrap(); + + let mut call_iter = CallFrameIter::new(&init_frame); + + let _call_1 = call_iter.next().unwrap(); + let _call_2 = call_iter.next().unwrap(); + call_iter.skip_children(); + + let _call_3 = call_iter.next().unwrap(); + let call_4 = call_iter.next().unwrap(); + assert_eq!(call_4.0.value, Some(U256::from(4))); + + let call_5 = call_iter.next().unwrap(); + assert_eq!(call_5.0.value, Some(U256::from(2))); + + let call_6 = call_iter.next(); + assert_eq!(call_6, None); + } } diff --git a/crates/rpc-types-trace/test_data/call_tracer/multi_call_default.json b/crates/rpc-types-trace/test_data/call_tracer/multi_call_default.json new file mode 100644 index 00000000000..d58191e0301 --- /dev/null +++ b/crates/rpc-types-trace/test_data/call_tracer/multi_call_default.json @@ -0,0 +1,112 @@ +{ + "calls": [ + { + "calls": [ + { + "calls": [ + { + "calls": [ + { + "calls": [ + { + "from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "gas": "0x6d05", + "gasUsed": "0x0", + "input": "0x", + "to": "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "type": "CALL", + "value": "0xb" + }, + { + "from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "gas": "0x6d05", + "gasUsed": "0x0", + "input": "0x", + "to": "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "type": "CALL", + "value": "0xa" + } + ], + "from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb", + "gas": "0x10738", + "gasUsed": "0x9751", + "input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "type": "CALL", + "value": "0x9" + }, + { + "from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "gas": "0x6d05", + "gasUsed": "0x0", + "input": "0x", + "to": "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "type": "CALL", + "value": "0x8" + } + ], + "from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "gas": "0x6d05", + "gasUsed": "0x0", + "input": "0x", + "to": "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "type": "CALL", + "value": "0x7" + }, + { + "from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "gas": "0x6d05", + "gasUsed": "0x0", + "input": "0x", + "to": "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "type": "CALL", + "value": "0x6" + } + ], + "from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb", + "gas": "0x10738", + "gasUsed": "0x9751", + "input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "type": "CALL", + "value": "0x5" + }, + { + "from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "gas": "0x6d05", + "gasUsed": "0x0", + "input": "0x", + "to": "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "type": "CALL", + "value": "0x4" + } + ], + "from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "gas": "0x6d05", + "gasUsed": "0x0", + "input": "0x", + "to": "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "type": "CALL", + "value": "0x3" + }, + { + "from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "gas": "0x6d05", + "gasUsed": "0x0", + "input": "0x", + "to": "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "type": "CALL", + "value": "0x2" + } + ], + "from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb", + "gas": "0x10738", + "gasUsed": "0x9751", + "input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "type": "CALL", + "value": "0x1" +} From 150f6020a1b57adda3c539ea4035ffaa87ae6d8e Mon Sep 17 00:00:00 2001 From: developeruche Date: Thu, 14 Aug 2025 15:58:16 +0100 Subject: [PATCH 2/9] clippy --- crates/rpc-types-trace/src/geth/call.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/rpc-types-trace/src/geth/call.rs b/crates/rpc-types-trace/src/geth/call.rs index 2bfbdd4f9f0..f848c930a5a 100644 --- a/crates/rpc-types-trace/src/geth/call.rs +++ b/crates/rpc-types-trace/src/geth/call.rs @@ -175,7 +175,7 @@ impl FlatCallConfig { /// An iterator for traversing `CallFrame` hierarchies. /// /// Traversal is **depth-first** by default. -/// You can skip children of the current frame via [`skip_children`]. +/// You can skip children of the current frame via `skip_children`. #[derive(Debug)] pub struct CallFrameIter<'a> { /// Stack of (frame reference, parent reference) @@ -191,7 +191,7 @@ impl<'a> CallFrameIter<'a> { /// Skip all children of the **current** frame. /// - /// This applies only to the *next* [`next`] call, + /// This applies only to the *next* `next` call, /// and will prevent traversal into its children. pub fn skip_children(&mut self) { // Mark the current depth so we can avoid pushing its children @@ -290,7 +290,7 @@ mod tests { let mut init_frame: CallFrame = serde_json::from_str(DEFAULT).unwrap(); init_frame.calls.push(init_frame.clone()); let init_frame_raw: CallFrame = serde_json::from_str(DEFAULT).unwrap(); - init_frame.calls[0].calls.push(init_frame_raw.clone()); + init_frame.calls[0].calls.push(init_frame_raw); let mut call_iter = CallFrameIter::new(&init_frame); From f1f87c02ac7fbfe09b2036fd63e7b2c9aedab850 Mon Sep 17 00:00:00 2001 From: developeruche Date: Tue, 19 Aug 2025 11:27:34 +0100 Subject: [PATCH 3/9] iter flow change, - inspect frame before skip" --- crates/rpc-types-trace/src/geth/call.rs | 269 +++++++++++++----------- 1 file changed, 143 insertions(+), 126 deletions(-) diff --git a/crates/rpc-types-trace/src/geth/call.rs b/crates/rpc-types-trace/src/geth/call.rs index f92a5c425ed..6e71abcc51c 100644 --- a/crates/rpc-types-trace/src/geth/call.rs +++ b/crates/rpc-types-trace/src/geth/call.rs @@ -173,53 +173,70 @@ impl FlatCallConfig { } } +/// A single item yielded by [`CallFrameIter`]. +/// +/// It gives access to the current frame, its parent, and +/// allows skipping traversal into its children. +#[derive(Debug)] +pub struct CallFrameItem<'a, 'iter> { + frame: &'a CallFrame, + parent: Option<&'a CallFrame>, + iter: &'iter mut CallFrameIter<'a>, + skip: bool, +} + +impl<'a, 'iter> CallFrameItem<'a, 'iter> { + /// The current frame. + pub fn frame(&self) -> &CallFrame { + self.frame + } + + /// The parent of this frame, if any. + pub fn parent(&self) -> Option<&CallFrame> { + self.parent + } + + /// Skip traversal into this frame's children. + pub fn skip_children(mut self) { + self.skip = true; + } +} + /// An iterator for traversing `CallFrame` hierarchies. /// /// Traversal is **depth-first** by default. -/// You can skip children of the current frame via `skip_children`. +/// Children of a frame can be skipped using [`CallFrameItem::skip_children`]. #[derive(Debug)] pub struct CallFrameIter<'a> { - /// Stack of (frame reference, parent reference) + /// Stack of (frame-item reference, parent reference) stack: Vec<(&'a CallFrame, Option<&'a CallFrame>)>, - skip_depth: Option, } impl<'a> CallFrameIter<'a> { /// Creates a new iterator starting from `root`. pub fn new(root: &'a CallFrame) -> Self { - Self { stack: vec![(root, None)], skip_depth: None } - } - - /// Skip all children of the **current** frame. - /// - /// This applies only to the *next* `next` call, - /// and will prevent traversal into its children. - pub fn skip_children(&mut self) { - // Mark the current depth so we can avoid pushing its children - self.skip_depth = Some(self.stack.len()); + Self { stack: vec![(root, None)] } } } impl<'a> Iterator for CallFrameIter<'a> { - type Item = (&'a CallFrame, Option<&'a CallFrame>); + type Item = CallFrameItem<'a, '_>; fn next(&mut self) -> Option { let (frame, parent) = self.stack.pop()?; - // Determine if we should skip children for this frame - let should_skip = - self.skip_depth.map(|depth| depth == self.stack.len() + 1).unwrap_or(false); + Some(CallFrameItem { frame, parent, iter: self, skip: false }) + } +} - if should_skip { - self.skip_depth = None; // reset skip state - } else { - // Push children in reverse so iteration order matches original vector - for child in frame.calls.iter().rev() { - self.stack.push((child, Some(frame))); +impl<'a, 'iter> Drop for CallFrameItem<'a, 'iter> { + fn drop(&mut self) { + // Only push children if skip was not called + if !self.skip { + for child in self.frame.calls.iter().rev() { + self.iter.stack.push((child, Some(self.frame))); } } - - Some((frame, parent)) } } @@ -349,104 +366,104 @@ impl From for CallType { } } -#[cfg(test)] -mod tests { - use super::*; - use crate::geth::*; - use similar_asserts::assert_eq; - - // See - const DEFAULT: &str = include_str!("../../test_data/call_tracer/default.json"); - const MULTI_DEFAULT: &str = include_str!("../../test_data/call_tracer/multi_call_default.json"); - const LEGACY: &str = include_str!("../../test_data/call_tracer/legacy.json"); - const ONLY_TOP_CALL: &str = include_str!("../../test_data/call_tracer/only_top_call.json"); - const WITH_LOG: &str = include_str!("../../test_data/call_tracer/with_log.json"); - - #[test] - fn test_serialize_call_trace() { - let mut opts = GethDebugTracingCallOptions::default(); - opts.tracing_options.config.disable_storage = Some(false); - opts.tracing_options.tracer = - Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::CallTracer)); - opts.tracing_options.tracer_config = - serde_json::to_value(CallConfig { only_top_call: Some(true), with_log: Some(true) }) - .unwrap() - .into(); - - assert_eq!( - serde_json::to_string(&opts).unwrap(), - r#"{"disableStorage":false,"tracer":"callTracer","tracerConfig":{"onlyTopCall":true,"withLog":true}}"# - ); - } - - #[test] - fn test_deserialize_call_trace() { - let _trace: CallFrame = serde_json::from_str(DEFAULT).unwrap(); - let _trace: CallFrame = serde_json::from_str(LEGACY).unwrap(); - let _trace: CallFrame = serde_json::from_str(ONLY_TOP_CALL).unwrap(); - let _trace: CallFrame = serde_json::from_str(WITH_LOG).unwrap(); - } - - #[test] - fn test_call_frame_iter() { - let mut init_frame: CallFrame = serde_json::from_str(DEFAULT).unwrap(); - init_frame.calls.push(init_frame.clone()); - - let mut call_iter = CallFrameIter::new(&init_frame); - - let call_1 = call_iter.next().unwrap(); - assert_eq!(call_1.0.calls.len(), 2); - assert_eq!(*call_1.0, init_frame); - - let call_2 = call_iter.next().unwrap(); - assert_eq!(call_2.0.calls.len(), 0); - let init_frame_raw: CallFrame = serde_json::from_str(DEFAULT).unwrap(); - assert_eq!(*call_2.0, init_frame_raw.calls[0]); - - let call_3 = call_iter.next().unwrap(); - assert_eq!(call_3.0.calls.len(), 1); - assert_eq!(*call_3.0, init_frame_raw); - - let call_4 = call_iter.next().unwrap(); - assert_eq!(call_4.0.calls.len(), 0); - assert_eq!(*call_4.0, init_frame_raw.calls[0]); - } - - #[test] - fn test_call_frame_iter_with_skip_child_1() { - let mut init_frame: CallFrame = serde_json::from_str(DEFAULT).unwrap(); - init_frame.calls.push(init_frame.clone()); - let init_frame_raw: CallFrame = serde_json::from_str(DEFAULT).unwrap(); - init_frame.calls[0].calls.push(init_frame_raw); - - let mut call_iter = CallFrameIter::new(&init_frame); - - call_iter.skip_children(); - - let _call_1 = call_iter.next().unwrap(); - let call_2 = call_iter.next(); - - assert_eq!(call_2, None); - } - - #[test] - fn test_call_frame_iter_with_skip_child_2() { - let init_frame: CallFrame = serde_json::from_str(MULTI_DEFAULT).unwrap(); - - let mut call_iter = CallFrameIter::new(&init_frame); - - let _call_1 = call_iter.next().unwrap(); - let _call_2 = call_iter.next().unwrap(); - call_iter.skip_children(); - - let _call_3 = call_iter.next().unwrap(); - let call_4 = call_iter.next().unwrap(); - assert_eq!(call_4.0.value, Some(U256::from(4))); - - let call_5 = call_iter.next().unwrap(); - assert_eq!(call_5.0.value, Some(U256::from(2))); - - let call_6 = call_iter.next(); - assert_eq!(call_6, None); - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::geth::*; +// use similar_asserts::assert_eq; + +// // See +// const DEFAULT: &str = include_str!("../../test_data/call_tracer/default.json"); +// const MULTI_DEFAULT: &str = include_str!("../../test_data/call_tracer/multi_call_default.json"); +// const LEGACY: &str = include_str!("../../test_data/call_tracer/legacy.json"); +// const ONLY_TOP_CALL: &str = include_str!("../../test_data/call_tracer/only_top_call.json"); +// const WITH_LOG: &str = include_str!("../../test_data/call_tracer/with_log.json"); + +// #[test] +// fn test_serialize_call_trace() { +// let mut opts = GethDebugTracingCallOptions::default(); +// opts.tracing_options.config.disable_storage = Some(false); +// opts.tracing_options.tracer = +// Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::CallTracer)); +// opts.tracing_options.tracer_config = +// serde_json::to_value(CallConfig { only_top_call: Some(true), with_log: Some(true) }) +// .unwrap() +// .into(); + +// assert_eq!( +// serde_json::to_string(&opts).unwrap(), +// r#"{"disableStorage":false,"tracer":"callTracer","tracerConfig":{"onlyTopCall":true,"withLog":true}}"# +// ); +// } + +// #[test] +// fn test_deserialize_call_trace() { +// let _trace: CallFrame = serde_json::from_str(DEFAULT).unwrap(); +// let _trace: CallFrame = serde_json::from_str(LEGACY).unwrap(); +// let _trace: CallFrame = serde_json::from_str(ONLY_TOP_CALL).unwrap(); +// let _trace: CallFrame = serde_json::from_str(WITH_LOG).unwrap(); +// } + +// #[test] +// fn test_call_frame_iter() { +// let mut init_frame: CallFrame = serde_json::from_str(DEFAULT).unwrap(); +// init_frame.calls.push(init_frame.clone()); + +// let mut call_iter = CallFrameIter::new(&init_frame); + +// let call_1 = call_iter.next().unwrap(); +// assert_eq!(call_1.0.calls.len(), 2); +// assert_eq!(*call_1.0, init_frame); + +// let call_2 = call_iter.next().unwrap(); +// assert_eq!(call_2.0.calls.len(), 0); +// let init_frame_raw: CallFrame = serde_json::from_str(DEFAULT).unwrap(); +// assert_eq!(*call_2.0, init_frame_raw.calls[0]); + +// let call_3 = call_iter.next().unwrap(); +// assert_eq!(call_3.0.calls.len(), 1); +// assert_eq!(*call_3.0, init_frame_raw); + +// let call_4 = call_iter.next().unwrap(); +// assert_eq!(call_4.0.calls.len(), 0); +// assert_eq!(*call_4.0, init_frame_raw.calls[0]); +// } + +// #[test] +// fn test_call_frame_iter_with_skip_child_1() { +// let mut init_frame: CallFrame = serde_json::from_str(DEFAULT).unwrap(); +// init_frame.calls.push(init_frame.clone()); +// let init_frame_raw: CallFrame = serde_json::from_str(DEFAULT).unwrap(); +// init_frame.calls[0].calls.push(init_frame_raw); + +// let mut call_iter = CallFrameIter::new(&init_frame); + +// call_iter.skip_children(); + +// let _call_1 = call_iter.next().unwrap(); +// let call_2 = call_iter.next(); + +// assert_eq!(call_2, None); +// } + +// #[test] +// fn test_call_frame_iter_with_skip_child_2() { +// let init_frame: CallFrame = serde_json::from_str(MULTI_DEFAULT).unwrap(); + +// let mut call_iter = CallFrameIter::new(&init_frame); + +// let _call_1 = call_iter.next().unwrap(); +// let _call_2 = call_iter.next().unwrap(); +// call_iter.skip_children(); + +// let _call_3 = call_iter.next().unwrap(); +// let call_4 = call_iter.next().unwrap(); +// assert_eq!(call_4.0.value, Some(U256::from(4))); + +// let call_5 = call_iter.next().unwrap(); +// assert_eq!(call_5.0.value, Some(U256::from(2))); + +// let call_6 = call_iter.next(); +// assert_eq!(call_6, None); +// } +// } From 822c32ddc22f43940bca4449297d2b43facb19dd Mon Sep 17 00:00:00 2001 From: developeruche Date: Tue, 19 Aug 2025 15:21:25 +0100 Subject: [PATCH 4/9] with delegate call example works fine now -- delegate_call --- crates/rpc-types-trace/src/geth/call.rs | 251 +++++++++--------- .../default_with_delegate_call.json | 21 ++ 2 files changed, 148 insertions(+), 124 deletions(-) create mode 100644 crates/rpc-types-trace/test_data/call_tracer/default_with_delegate_call.json diff --git a/crates/rpc-types-trace/src/geth/call.rs b/crates/rpc-types-trace/src/geth/call.rs index 6e71abcc51c..33b39743f66 100644 --- a/crates/rpc-types-trace/src/geth/call.rs +++ b/crates/rpc-types-trace/src/geth/call.rs @@ -177,15 +177,13 @@ impl FlatCallConfig { /// /// It gives access to the current frame, its parent, and /// allows skipping traversal into its children. -#[derive(Debug)] -pub struct CallFrameItem<'a, 'iter> { +#[derive(Debug, PartialEq)] +pub struct CallFrameItem<'a> { frame: &'a CallFrame, parent: Option<&'a CallFrame>, - iter: &'iter mut CallFrameIter<'a>, - skip: bool, } -impl<'a, 'iter> CallFrameItem<'a, 'iter> { +impl<'a> CallFrameItem<'a> { /// The current frame. pub fn frame(&self) -> &CallFrame { self.frame @@ -195,11 +193,6 @@ impl<'a, 'iter> CallFrameItem<'a, 'iter> { pub fn parent(&self) -> Option<&CallFrame> { self.parent } - - /// Skip traversal into this frame's children. - pub fn skip_children(mut self) { - self.skip = true; - } } /// An iterator for traversing `CallFrame` hierarchies. @@ -210,33 +203,42 @@ impl<'a, 'iter> CallFrameItem<'a, 'iter> { pub struct CallFrameIter<'a> { /// Stack of (frame-item reference, parent reference) stack: Vec<(&'a CallFrame, Option<&'a CallFrame>)>, + /// Whether to skip children for the most recently yielded item + skip_children: bool, } impl<'a> CallFrameIter<'a> { /// Creates a new iterator starting from `root`. pub fn new(root: &'a CallFrame) -> Self { - Self { stack: vec![(root, None)] } + Self { stack: vec![(root, None)], skip_children: false } + } + + /// Skips children for the most recently yielded item. + /// Note: this would panic if there are no parent owning the children to skip. + pub fn skip_children(&mut self) { + let parent = self.stack.last().map(|&(_, parent)| parent).unwrap().unwrap(); + self.stack = self.stack.split_off(parent.calls.len()); } } impl<'a> Iterator for CallFrameIter<'a> { - type Item = CallFrameItem<'a, '_>; + type Item = CallFrameItem<'a>; fn next(&mut self) -> Option { let (frame, parent) = self.stack.pop()?; - Some(CallFrameItem { frame, parent, iter: self, skip: false }) - } -} - -impl<'a, 'iter> Drop for CallFrameItem<'a, 'iter> { - fn drop(&mut self) { - // Only push children if skip was not called - if !self.skip { - for child in self.frame.calls.iter().rev() { - self.iter.stack.push((child, Some(self.frame))); - } + // Add children in reverse order so they're processed in the correct order + for child in frame.calls.iter().rev() { + self.stack.push((child, Some(frame))); } + + // Reset skip_children flag + self.skip_children = false; + + // Create and return the item + let item = CallFrameItem { frame, parent }; + + Some(item) } } @@ -366,104 +368,105 @@ impl From for CallType { } } -// #[cfg(test)] -// mod tests { -// use super::*; -// use crate::geth::*; -// use similar_asserts::assert_eq; - -// // See -// const DEFAULT: &str = include_str!("../../test_data/call_tracer/default.json"); -// const MULTI_DEFAULT: &str = include_str!("../../test_data/call_tracer/multi_call_default.json"); -// const LEGACY: &str = include_str!("../../test_data/call_tracer/legacy.json"); -// const ONLY_TOP_CALL: &str = include_str!("../../test_data/call_tracer/only_top_call.json"); -// const WITH_LOG: &str = include_str!("../../test_data/call_tracer/with_log.json"); - -// #[test] -// fn test_serialize_call_trace() { -// let mut opts = GethDebugTracingCallOptions::default(); -// opts.tracing_options.config.disable_storage = Some(false); -// opts.tracing_options.tracer = -// Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::CallTracer)); -// opts.tracing_options.tracer_config = -// serde_json::to_value(CallConfig { only_top_call: Some(true), with_log: Some(true) }) -// .unwrap() -// .into(); - -// assert_eq!( -// serde_json::to_string(&opts).unwrap(), -// r#"{"disableStorage":false,"tracer":"callTracer","tracerConfig":{"onlyTopCall":true,"withLog":true}}"# -// ); -// } - -// #[test] -// fn test_deserialize_call_trace() { -// let _trace: CallFrame = serde_json::from_str(DEFAULT).unwrap(); -// let _trace: CallFrame = serde_json::from_str(LEGACY).unwrap(); -// let _trace: CallFrame = serde_json::from_str(ONLY_TOP_CALL).unwrap(); -// let _trace: CallFrame = serde_json::from_str(WITH_LOG).unwrap(); -// } - -// #[test] -// fn test_call_frame_iter() { -// let mut init_frame: CallFrame = serde_json::from_str(DEFAULT).unwrap(); -// init_frame.calls.push(init_frame.clone()); - -// let mut call_iter = CallFrameIter::new(&init_frame); - -// let call_1 = call_iter.next().unwrap(); -// assert_eq!(call_1.0.calls.len(), 2); -// assert_eq!(*call_1.0, init_frame); - -// let call_2 = call_iter.next().unwrap(); -// assert_eq!(call_2.0.calls.len(), 0); -// let init_frame_raw: CallFrame = serde_json::from_str(DEFAULT).unwrap(); -// assert_eq!(*call_2.0, init_frame_raw.calls[0]); - -// let call_3 = call_iter.next().unwrap(); -// assert_eq!(call_3.0.calls.len(), 1); -// assert_eq!(*call_3.0, init_frame_raw); - -// let call_4 = call_iter.next().unwrap(); -// assert_eq!(call_4.0.calls.len(), 0); -// assert_eq!(*call_4.0, init_frame_raw.calls[0]); -// } - -// #[test] -// fn test_call_frame_iter_with_skip_child_1() { -// let mut init_frame: CallFrame = serde_json::from_str(DEFAULT).unwrap(); -// init_frame.calls.push(init_frame.clone()); -// let init_frame_raw: CallFrame = serde_json::from_str(DEFAULT).unwrap(); -// init_frame.calls[0].calls.push(init_frame_raw); - -// let mut call_iter = CallFrameIter::new(&init_frame); - -// call_iter.skip_children(); - -// let _call_1 = call_iter.next().unwrap(); -// let call_2 = call_iter.next(); - -// assert_eq!(call_2, None); -// } - -// #[test] -// fn test_call_frame_iter_with_skip_child_2() { -// let init_frame: CallFrame = serde_json::from_str(MULTI_DEFAULT).unwrap(); - -// let mut call_iter = CallFrameIter::new(&init_frame); - -// let _call_1 = call_iter.next().unwrap(); -// let _call_2 = call_iter.next().unwrap(); -// call_iter.skip_children(); - -// let _call_3 = call_iter.next().unwrap(); -// let call_4 = call_iter.next().unwrap(); -// assert_eq!(call_4.0.value, Some(U256::from(4))); - -// let call_5 = call_iter.next().unwrap(); -// assert_eq!(call_5.0.value, Some(U256::from(2))); - -// let call_6 = call_iter.next(); -// assert_eq!(call_6, None); -// } -// } +#[cfg(test)] +mod tests { + use super::*; + use crate::geth::*; + use similar_asserts::assert_eq; + + // See + const DEFAULT: &str = include_str!("../../test_data/call_tracer/default.json"); + const MULTI_DEFAULT: &str = include_str!("../../test_data/call_tracer/multi_call_default.json"); + const DELEGATE_DEFAULT: &str = + include_str!("../../test_data/call_tracer/default_with_delegate_call.json"); + const LEGACY: &str = include_str!("../../test_data/call_tracer/legacy.json"); + const ONLY_TOP_CALL: &str = include_str!("../../test_data/call_tracer/only_top_call.json"); + const WITH_LOG: &str = include_str!("../../test_data/call_tracer/with_log.json"); + + #[test] + fn test_serialize_call_trace() { + let mut opts = GethDebugTracingCallOptions::default(); + opts.tracing_options.config.disable_storage = Some(false); + opts.tracing_options.tracer = + Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::CallTracer)); + opts.tracing_options.tracer_config = + serde_json::to_value(CallConfig { only_top_call: Some(true), with_log: Some(true) }) + .unwrap() + .into(); + + assert_eq!( + serde_json::to_string(&opts).unwrap(), + r#"{"disableStorage":false,"tracer":"callTracer","tracerConfig":{"onlyTopCall":true,"withLog":true}}"# + ); + } + + #[test] + fn test_deserialize_call_trace() { + let _trace: CallFrame = serde_json::from_str(DEFAULT).unwrap(); + let _trace: CallFrame = serde_json::from_str(LEGACY).unwrap(); + let _trace: CallFrame = serde_json::from_str(ONLY_TOP_CALL).unwrap(); + let _trace: CallFrame = serde_json::from_str(WITH_LOG).unwrap(); + } + + #[test] + fn test_call_frame_iter() { + let mut init_frame: CallFrame = serde_json::from_str(DEFAULT).unwrap(); + init_frame.calls.push(init_frame.clone()); + + let mut call_iter = CallFrameIter::new(&init_frame); + + let call_1 = call_iter.next().unwrap(); + assert_eq!(call_1.frame().calls.len(), 2); + assert_eq!(*call_1.frame(), init_frame); + + let call_2 = call_iter.next().unwrap(); + assert_eq!(call_2.frame().calls.len(), 0); + let init_frame_raw: CallFrame = serde_json::from_str(DEFAULT).unwrap(); + assert_eq!(*call_2.frame(), init_frame_raw.calls[0]); + + let call_3 = call_iter.next().unwrap(); + assert_eq!(call_3.frame().calls.len(), 1); + assert_eq!(*call_3.frame(), init_frame_raw); + + let call_4 = call_iter.next().unwrap(); + assert_eq!(call_4.frame().calls.len(), 0); + assert_eq!(*call_4.frame(), init_frame_raw.calls[0]); + } + + #[test] + fn test_call_frame_iter_with_delegate_call() { + let init_frame: CallFrame = serde_json::from_str(DELEGATE_DEFAULT).unwrap(); + + let mut call_iter = CallFrameIter::new(&init_frame); + + let call_1 = call_iter.next().unwrap(); + println!("call One: {:?}", call_1.frame()); + if call_1.frame().is_delegate_call() { + call_iter.skip_children(); + } + + let call_2 = call_iter.next(); + assert_eq!(call_2, None); + } + + // #[test] + // fn test_call_frame_iter_with_skip_child_2() { + // let init_frame: CallFrame = serde_json::from_str(MULTI_DEFAULT).unwrap(); + + // let mut call_iter = CallFrameIter::new(&init_frame); + + // let _call_1 = call_iter.next().unwrap(); + // let _call_2 = call_iter.next().unwrap(); + // call_iter.skip_children(); + + // let _call_3 = call_iter.next().unwrap(); + // let call_4 = call_iter.next().unwrap(); + // assert_eq!(call_4.0.value, Some(U256::from(4))); + + // let call_5 = call_iter.next().unwrap(); + // assert_eq!(call_5.0.value, Some(U256::from(2))); + + // let call_6 = call_iter.next(); + // assert_eq!(call_6, None); + // } +} diff --git a/crates/rpc-types-trace/test_data/call_tracer/default_with_delegate_call.json b/crates/rpc-types-trace/test_data/call_tracer/default_with_delegate_call.json new file mode 100644 index 00000000000..20b8c04fb3c --- /dev/null +++ b/crates/rpc-types-trace/test_data/call_tracer/default_with_delegate_call.json @@ -0,0 +1,21 @@ +{ + "calls": [ + { + "from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "gas": "0x6d05", + "gasUsed": "0x0", + "input": "0x", + "to": "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "type": "CALL", + "value": "0x6f05b59d3b20000" + } + ], + "from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb", + "gas": "0x10738", + "gasUsed": "0x9751", + "input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "type": "DELEGATECALL", + "value": "0x0" +} From f9a8f5e6252d973d1435d34fff2eb5c24184dc92 Mon Sep 17 00:00:00 2001 From: developeruche Date: Tue, 19 Aug 2025 16:44:46 +0100 Subject: [PATCH 5/9] skip_children with multiple --- crates/rpc-types-trace/src/geth/call.rs | 52 +++++++++++++------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/crates/rpc-types-trace/src/geth/call.rs b/crates/rpc-types-trace/src/geth/call.rs index 33b39743f66..5fba2f47570 100644 --- a/crates/rpc-types-trace/src/geth/call.rs +++ b/crates/rpc-types-trace/src/geth/call.rs @@ -198,26 +198,26 @@ impl<'a> CallFrameItem<'a> { /// An iterator for traversing `CallFrame` hierarchies. /// /// Traversal is **depth-first** by default. -/// Children of a frame can be skipped using [`CallFrameItem::skip_children`]. +/// Children of a frame can be skipped using [`CallFrameIter::skip_children`]. #[derive(Debug)] pub struct CallFrameIter<'a> { /// Stack of (frame-item reference, parent reference) stack: Vec<(&'a CallFrame, Option<&'a CallFrame>)>, - /// Whether to skip children for the most recently yielded item - skip_children: bool, + /// The last frame that was yielded. + last_frame: Option<&'a CallFrame>, } impl<'a> CallFrameIter<'a> { /// Creates a new iterator starting from `root`. pub fn new(root: &'a CallFrame) -> Self { - Self { stack: vec![(root, None)], skip_children: false } + Self { stack: vec![(root, None)], last_frame: None } } /// Skips children for the most recently yielded item. /// Note: this would panic if there are no parent owning the children to skip. pub fn skip_children(&mut self) { - let parent = self.stack.last().map(|&(_, parent)| parent).unwrap().unwrap(); - self.stack = self.stack.split_off(parent.calls.len()); + let parent = self.last_frame.unwrap(); + let _ = self.stack.split_off(parent.calls.len()); } } @@ -232,11 +232,9 @@ impl<'a> Iterator for CallFrameIter<'a> { self.stack.push((child, Some(frame))); } - // Reset skip_children flag - self.skip_children = false; - // Create and return the item let item = CallFrameItem { frame, parent }; + self.last_frame = Some(frame); Some(item) } @@ -449,24 +447,30 @@ mod tests { assert_eq!(call_2, None); } - // #[test] - // fn test_call_frame_iter_with_skip_child_2() { - // let init_frame: CallFrame = serde_json::from_str(MULTI_DEFAULT).unwrap(); + #[test] + fn test_call_frame_iter_with_multiple_skip_children() { + let init_frame: CallFrame = serde_json::from_str(MULTI_DEFAULT).unwrap(); - // let mut call_iter = CallFrameIter::new(&init_frame); + let mut call_iter = CallFrameIter::new(&init_frame); - // let _call_1 = call_iter.next().unwrap(); - // let _call_2 = call_iter.next().unwrap(); - // call_iter.skip_children(); + let call_1 = call_iter.next().unwrap(); + assert_eq!(call_1.frame().value, Some(U256::from(1))); - // let _call_3 = call_iter.next().unwrap(); - // let call_4 = call_iter.next().unwrap(); - // assert_eq!(call_4.0.value, Some(U256::from(4))); + let call_2 = call_iter.next().unwrap(); + assert_eq!(call_2.frame().value, Some(U256::from(3))); - // let call_5 = call_iter.next().unwrap(); - // assert_eq!(call_5.0.value, Some(U256::from(2))); + let call_3 = call_iter.next().unwrap(); + assert_eq!(call_3.frame().value, Some(U256::from(5))); - // let call_6 = call_iter.next(); - // assert_eq!(call_6, None); - // } + call_iter.skip_children(); + + let call_4 = call_iter.next().unwrap(); + assert_eq!(call_4.frame().value, Some(U256::from(4))); + + let call_5 = call_iter.next().unwrap(); + assert_eq!(call_5.frame().value, Some(U256::from(2))); + + let call_6 = call_iter.next(); + assert_eq!(call_6, None); + } } From 868d807d10ff603d0e0903a5432788d07fce277a Mon Sep 17 00:00:00 2001 From: developeruche Date: Tue, 19 Aug 2025 17:02:33 +0100 Subject: [PATCH 6/9] fixed slipting bug --- crates/rpc-types-trace/src/geth/call.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/rpc-types-trace/src/geth/call.rs b/crates/rpc-types-trace/src/geth/call.rs index 5fba2f47570..8a315db1133 100644 --- a/crates/rpc-types-trace/src/geth/call.rs +++ b/crates/rpc-types-trace/src/geth/call.rs @@ -214,10 +214,11 @@ impl<'a> CallFrameIter<'a> { } /// Skips children for the most recently yielded item. - /// Note: this would panic if there are no parent owning the children to skip. + /// Note: this would panic if there are no parent owning the children to + /// skip. `next` must be called before `skip_children`. pub fn skip_children(&mut self) { let parent = self.last_frame.unwrap(); - let _ = self.stack.split_off(parent.calls.len()); + let _ = self.stack.split_off(self.stack.len() - parent.calls.len()); } } @@ -438,9 +439,10 @@ mod tests { let mut call_iter = CallFrameIter::new(&init_frame); let call_1 = call_iter.next().unwrap(); - println!("call One: {:?}", call_1.frame()); if call_1.frame().is_delegate_call() { + println!("Here: {:?}", call_iter.stack.len()); call_iter.skip_children(); + println!("Here: {:?}", call_iter.stack.len()); } let call_2 = call_iter.next(); From a57df55135534535022948424c6af2dccf29f51f Mon Sep 17 00:00:00 2001 From: developeruche Date: Tue, 19 Aug 2025 17:04:47 +0100 Subject: [PATCH 7/9] clean --- crates/rpc-types-trace/src/geth/call.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/rpc-types-trace/src/geth/call.rs b/crates/rpc-types-trace/src/geth/call.rs index 8a315db1133..d47d70b4bae 100644 --- a/crates/rpc-types-trace/src/geth/call.rs +++ b/crates/rpc-types-trace/src/geth/call.rs @@ -440,9 +440,7 @@ mod tests { let call_1 = call_iter.next().unwrap(); if call_1.frame().is_delegate_call() { - println!("Here: {:?}", call_iter.stack.len()); call_iter.skip_children(); - println!("Here: {:?}", call_iter.stack.len()); } let call_2 = call_iter.next(); From 280a5c7417d88a6fbed12b71a4c191151616a48b Mon Sep 17 00:00:00 2001 From: developeruche Date: Tue, 19 Aug 2025 17:17:03 +0100 Subject: [PATCH 8/9] clippy --- crates/rpc-types-trace/src/geth/call.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/rpc-types-trace/src/geth/call.rs b/crates/rpc-types-trace/src/geth/call.rs index d47d70b4bae..759ad5a2717 100644 --- a/crates/rpc-types-trace/src/geth/call.rs +++ b/crates/rpc-types-trace/src/geth/call.rs @@ -185,12 +185,12 @@ pub struct CallFrameItem<'a> { impl<'a> CallFrameItem<'a> { /// The current frame. - pub fn frame(&self) -> &CallFrame { + pub const fn frame(&self) -> &CallFrame { self.frame } /// The parent of this frame, if any. - pub fn parent(&self) -> Option<&CallFrame> { + pub const fn parent(&self) -> Option<&CallFrame> { self.parent } } From 6199093ddc4f35f8128f19b27219fe5f7f244144 Mon Sep 17 00:00:00 2001 From: developeruche Date: Thu, 28 Aug 2025 16:42:42 +0100 Subject: [PATCH 9/9] removed unwrap --- crates/rpc-types-trace/src/geth/call.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/rpc-types-trace/src/geth/call.rs b/crates/rpc-types-trace/src/geth/call.rs index 759ad5a2717..9ae611430e0 100644 --- a/crates/rpc-types-trace/src/geth/call.rs +++ b/crates/rpc-types-trace/src/geth/call.rs @@ -217,8 +217,9 @@ impl<'a> CallFrameIter<'a> { /// Note: this would panic if there are no parent owning the children to /// skip. `next` must be called before `skip_children`. pub fn skip_children(&mut self) { - let parent = self.last_frame.unwrap(); - let _ = self.stack.split_off(self.stack.len() - parent.calls.len()); + if let Some(parent) = self.last_frame { + let _ = self.stack.split_off(self.stack.len() - parent.calls.len()); + } } }