Skip to content
114 changes: 114 additions & 0 deletions crates/rpc-types-trace/src/geth/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,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<usize>,
}

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<Self::Item> {
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))
}
}

/// A unified representation of a call.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
Expand Down Expand Up @@ -307,6 +357,7 @@ mod tests {

// See <https://github.com/ethereum/go-ethereum/tree/master/eth/tracers/internal/tracetest/testdata>
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");
Expand Down Expand Up @@ -335,4 +386,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);

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);
}
}
112 changes: 112 additions & 0 deletions crates/rpc-types-trace/test_data/call_tracer/multi_call_default.json
Original file line number Diff line number Diff line change
@@ -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"
}
Loading