1use soldb_core::{SoldbError, SoldbResult, TransactionTrace};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct RootCall {
14 pub entry_step: usize,
16 pub record: RootRecord,
17 pub kind: Option<String>,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum RootRecord {
24 Trace,
26 Call(usize),
28 Creation(usize),
30}
31
32impl RootCall {
33 #[must_use]
36 pub fn address<'a>(&self, trace: &'a TransactionTrace) -> Option<&'a str> {
37 match self.record {
38 RootRecord::Trace => trace
39 .to_addr
40 .as_deref()
41 .or(trace.contract_address.as_deref()),
42 RootRecord::Call(index) => trace
43 .artifacts
44 .calls
45 .get(index)
46 .map(|call| call.bytecode_address.as_str()),
47 RootRecord::Creation(index) => trace
48 .artifacts
49 .creations
50 .get(index)
51 .and_then(|creation| creation.address.as_deref()),
52 }
53 }
54
55 #[must_use]
57 pub fn creation(&self, trace: &TransactionTrace) -> bool {
58 match self.record {
59 RootRecord::Trace => trace.to_addr.is_none() && trace.contract_address.is_some(),
60 RootRecord::Call(_) => false,
61 RootRecord::Creation(_) => true,
62 }
63 }
64
65 #[must_use]
67 pub fn input<'a>(&self, trace: &'a TransactionTrace) -> &'a str {
68 match self.record {
69 RootRecord::Trace => &trace.input_data,
70 RootRecord::Call(index) => trace
71 .artifacts
72 .calls
73 .get(index)
74 .map_or("", |call| call.input.as_str()),
75 RootRecord::Creation(index) => trace
76 .artifacts
77 .creations
78 .get(index)
79 .map_or("", |creation| creation.init_code.as_str()),
80 }
81 }
82}
83
84#[must_use]
86pub fn root_calls(trace: &TransactionTrace) -> Vec<RootCall> {
87 let mut roots = Vec::new();
88 for (index, call) in trace.artifacts.calls.iter().enumerate() {
89 if call.parent_id.is_some() {
90 continue;
91 }
92 let Some(entry_step) = root_entry(trace, call.entry_step, call.exit_step) else {
93 continue;
94 };
95 roots.push(RootCall {
96 entry_step,
97 record: RootRecord::Call(index),
98 kind: None,
99 });
100 }
101 for (index, creation) in trace.artifacts.creations.iter().enumerate() {
102 if creation.parent_id.is_some() {
103 continue;
104 }
105 let Some(entry_step) = root_entry(trace, creation.entry_step, creation.exit_step) else {
106 continue;
107 };
108 roots.push(RootCall {
109 entry_step,
110 record: RootRecord::Creation(index),
111 kind: None,
112 });
113 }
114 roots.sort_by_key(|root| root.entry_step);
115 if roots.first().is_none_or(|root| root.entry_step != 0) {
117 roots.insert(
118 0,
119 RootCall {
120 entry_step: 0,
121 record: RootRecord::Trace,
122 kind: None,
123 },
124 );
125 }
126 for root in &mut roots {
127 let kind = trace
128 .artifacts
129 .root_kinds
130 .iter()
131 .find(|kind| kind.entry_step == root.entry_step);
132 root.kind = kind.map(|kind| kind.kind.clone());
133 }
134 roots
135}
136
137fn root_entry(
141 trace: &TransactionTrace,
142 entry: Option<usize>,
143 exit: Option<usize>,
144) -> Option<usize> {
145 let entry = entry?;
146 if exit.is_some_and(|exit| exit <= entry) {
147 return None;
148 }
149 let root_depth = trace.steps.first().map_or(0, |step| step.depth);
150 let step = trace.steps.get(entry)?;
151 (step.depth == root_depth).then_some(entry)
152}
153
154pub fn check_root_calls(trace: &TransactionTrace) -> SoldbResult<()> {
156 let roots = root_calls(trace);
157 let Some(pair) = roots
158 .windows(2)
159 .find(|pair| pair[0].entry_step == pair[1].entry_step)
160 else {
161 return Ok(());
162 };
163 Err(SoldbError::Message(format!(
164 "invalid trace: two top-level calls start at step {}\n\
165 the trace records more than one call or creation without a parent entered there",
166 pair[0].entry_step
167 )))
168}
169
170#[must_use]
172pub fn root_index(roots: &[RootCall], step: usize) -> usize {
173 let position = roots.partition_point(|root| root.entry_step <= step);
174 position.saturating_sub(1)
175}
176
177#[cfg(test)]
178mod tests {
179 use soldb_core::{
180 ContractCreation, ExecutionCall, RootKind, StepSnapshot, TraceStep, TransactionTrace,
181 };
182
183 use super::{check_root_calls, root_calls, root_index, RootCall, RootRecord};
184
185 fn step(depth: u64) -> TraceStep {
186 TraceStep::new(0, "STOP".into(), 0, 0, depth, None, StepSnapshot::default())
187 }
188
189 fn trace(depths: &[u64]) -> TransactionTrace {
190 TransactionTrace {
191 tx_hash: None,
192 from_addr: "0x1".to_owned(),
193 to_addr: Some("0xaa".to_owned()),
194 value: "0x0".to_owned(),
195 input_data: "0x01".to_owned(),
196 gas_used: 0,
197 output: "0x".to_owned(),
198 success: true,
199 error: None,
200 debug_trace_available: true,
201 contract_address: None,
202 backend: None,
203 capabilities: Default::default(),
204 artifacts: Default::default(),
205 steps: depths.iter().map(|depth| step(*depth)).collect(),
206 }
207 }
208
209 fn call(parent_id: Option<usize>, entry: usize, exit: usize, address: &str) -> ExecutionCall {
210 ExecutionCall {
211 id: 0,
212 parent_id,
213 depth: 1,
214 entry_step: Some(entry),
215 exit_step: Some(exit),
216 call_type: "CALL".to_owned(),
217 from: "0x1".to_owned(),
218 to: address.to_owned(),
219 bytecode_address: address.to_owned(),
220 value: "0x0".to_owned(),
221 input: format!("0x{entry:02x}"),
222 gas_limit: 0,
223 gas_used: None,
224 output: None,
225 success: Some(true),
226 error: None,
227 }
228 }
229
230 #[test]
231 fn without_records_the_trace_fields_are_the_root() {
232 let trace = trace(&[1, 1]);
233 let roots = root_calls(&trace);
234 assert_eq!(
235 roots,
236 vec![RootCall {
237 entry_step: 0,
238 record: RootRecord::Trace,
239 kind: None,
240 }]
241 );
242 assert_eq!(roots[0].address(&trace), Some("0xaa"));
243 assert!(!roots[0].creation(&trace));
244 assert_eq!(roots[0].input(&trace), "0x01");
245 }
246
247 #[test]
248 fn top_level_records_are_roots_in_execution_order() {
249 let mut trace = trace(&[1, 1, 2, 1, 1]);
250 trace.artifacts.calls = vec![
251 call(None, 3, 5, "0xbb"),
252 call(None, 0, 3, "0xaa"),
253 call(Some(1), 2, 3, "0xcc"),
255 call(None, 2, 3, "0xdd"),
257 call(None, 5, 5, "0xee"),
259 ];
260 let roots = root_calls(&trace);
261 let entries = roots.iter().map(|root| root.entry_step).collect::<Vec<_>>();
262 assert_eq!(entries, vec![0, 3]);
263 assert_eq!(roots[root_index(&roots, 2)].address(&trace), Some("0xaa"));
264 assert_eq!(roots[root_index(&roots, 4)].input(&trace), "0x03");
265 }
266
267 #[test]
268 fn two_roots_starting_at_one_step_are_invalid() {
269 let mut trace = trace(&[1, 1, 1, 1]);
270 trace.artifacts.calls = vec![
271 call(None, 0, 1, "0xaa"),
272 call(None, 1, 2, "0xbb"),
273 call(None, 2, 4, "0xdd"),
274 ];
275 assert!(check_root_calls(&trace).is_ok());
276 trace.artifacts.calls.push(call(None, 1, 2, "0xcc"));
277 let error = check_root_calls(&trace).expect_err("two roots at step 1");
278 assert!(error
279 .to_string()
280 .starts_with("invalid trace: two top-level calls start at step 1\n"));
281 }
282
283 #[test]
284 fn a_later_root_creation_follows_the_trace_root() {
285 let mut trace = trace(&[1, 1]);
286 trace.artifacts.creations.push(ContractCreation {
287 id: 0,
288 parent_id: None,
289 depth: 1,
290 entry_step: Some(1),
291 exit_step: None,
292 create_type: "CREATE".to_owned(),
293 caller: "0x1".to_owned(),
294 address: Some("0xcc".to_owned()),
295 value: "0x0".to_owned(),
296 init_code: "0x6000".to_owned(),
297 gas_limit: 0,
298 gas_used: None,
299 output: None,
300 success: Some(true),
301 error: None,
302 });
303 let roots = root_calls(&trace);
304 assert_eq!(roots.len(), 2);
305 assert_eq!(roots[0].input(&trace), "0x01");
306 assert!(roots[1].creation(&trace));
307 assert_eq!(roots[1].address(&trace), Some("0xcc"));
308 assert_eq!(roots[1].input(&trace), "0x6000");
309 }
310
311 #[test]
312 fn roots_carry_the_kind_recorded_at_their_entry_step() {
313 let mut trace = trace(&[1, 1, 1]);
314 trace.artifacts.calls = vec![call(None, 0, 2, "0xaa"), call(None, 2, 3, "0xbb")];
315 trace.artifacts.root_kinds = vec![
316 RootKind {
317 entry_step: 2,
318 kind: "execution".to_owned(),
319 },
320 RootKind {
321 entry_step: 0,
322 kind: "setup".to_owned(),
323 },
324 RootKind {
326 entry_step: 1,
327 kind: "deployment".to_owned(),
328 },
329 ];
330 let kinds = root_calls(&trace)
331 .into_iter()
332 .map(|root| root.kind)
333 .collect::<Vec<_>>();
334 assert_eq!(
335 kinds,
336 vec![Some("setup".to_owned()), Some("execution".to_owned())]
337 );
338 }
339}