1use std::collections::HashSet;
29use std::sync::Arc;
30
31use async_trait::async_trait;
32use serde::{Deserialize, Serialize};
33
34use crate::error::IndexerError;
35use crate::types::IndexContext;
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
41pub enum CallType {
42 Call,
44 DelegateCall,
46 StaticCall,
48 Create,
50 Create2,
52 SelfDestruct,
54}
55
56impl CallType {
57 pub fn from_geth(s: &str) -> Option<Self> {
61 match s.to_uppercase().as_str() {
62 "CALL" => Some(Self::Call),
63 "DELEGATECALL" => Some(Self::DelegateCall),
64 "STATICCALL" => Some(Self::StaticCall),
65 "CREATE" => Some(Self::Create),
66 "CREATE2" => Some(Self::Create2),
67 "SELFDESTRUCT" => Some(Self::SelfDestruct),
68 _ => None,
69 }
70 }
71
72 pub fn from_parity(s: &str) -> Option<Self> {
77 match s.to_lowercase().as_str() {
78 "call" => Some(Self::Call),
79 "delegatecall" => Some(Self::DelegateCall),
80 "staticcall" => Some(Self::StaticCall),
81 "create" => Some(Self::Create),
82 "create2" => Some(Self::Create2),
83 "suicide" | "selfdestruct" => Some(Self::SelfDestruct),
84 _ => None,
85 }
86 }
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct CallTrace {
97 pub call_type: CallType,
99 pub from: String,
101 pub to: String,
103 pub value: String,
105 pub gas_used: u64,
107 pub input: String,
109 pub output: String,
111 pub function_selector: Option<String>,
114 pub depth: u32,
116 pub block_number: u64,
118 pub tx_hash: String,
120 pub tx_index: u32,
122 pub trace_index: u32,
124 pub error: Option<String>,
126 pub reverted: bool,
128}
129
130impl CallTrace {
131 pub fn extract_selector(input: &str) -> Option<String> {
136 let hex = input.strip_prefix("0x").unwrap_or(input);
137 if hex.len() >= 8 {
138 Some(format!("0x{}", &hex[..8].to_lowercase()))
139 } else {
140 None
141 }
142 }
143}
144
145#[derive(Debug, Clone, Default, Serialize, Deserialize)]
152pub struct TraceFilter {
153 pub addresses: HashSet<String>,
156 pub selectors: HashSet<String>,
159 pub call_types: HashSet<CallType>,
161 pub exclude_reverted: bool,
163 pub min_depth: Option<u32>,
165 pub max_depth: Option<u32>,
167}
168
169impl TraceFilter {
170 pub fn new() -> Self {
172 Self::default()
173 }
174
175 pub fn with_address(mut self, addr: impl Into<String>) -> Self {
177 self.addresses.insert(addr.into().to_lowercase());
178 self
179 }
180
181 pub fn with_selector(mut self, selector: impl Into<String>) -> Self {
183 self.selectors.insert(selector.into().to_lowercase());
184 self
185 }
186
187 pub fn with_call_type(mut self, call_type: CallType) -> Self {
189 self.call_types.insert(call_type);
190 self
191 }
192
193 pub fn exclude_reverted(mut self, exclude: bool) -> Self {
195 self.exclude_reverted = exclude;
196 self
197 }
198
199 pub fn min_depth(mut self, depth: u32) -> Self {
201 self.min_depth = Some(depth);
202 self
203 }
204
205 pub fn max_depth(mut self, depth: u32) -> Self {
207 self.max_depth = Some(depth);
208 self
209 }
210
211 pub fn matches(&self, trace: &CallTrace) -> bool {
213 if self.exclude_reverted && trace.reverted {
215 return false;
216 }
217
218 if !self.call_types.is_empty() && !self.call_types.contains(&trace.call_type) {
220 return false;
221 }
222
223 if !self.addresses.is_empty() {
225 let from_lower = trace.from.to_lowercase();
226 let to_lower = trace.to.to_lowercase();
227 if !self.addresses.contains(&from_lower) && !self.addresses.contains(&to_lower) {
228 return false;
229 }
230 }
231
232 if !self.selectors.is_empty() {
234 match &trace.function_selector {
235 Some(sel) => {
236 if !self.selectors.contains(&sel.to_lowercase()) {
237 return false;
238 }
239 }
240 None => return false,
241 }
242 }
243
244 if let Some(min) = self.min_depth {
246 if trace.depth < min {
247 return false;
248 }
249 }
250 if let Some(max) = self.max_depth {
251 if trace.depth > max {
252 return false;
253 }
254 }
255
256 true
257 }
258}
259
260#[async_trait]
267pub trait TraceHandler: Send + Sync {
268 async fn handle_trace(&self, trace: &CallTrace, ctx: &IndexContext)
270 -> Result<(), IndexerError>;
271
272 fn name(&self) -> &str;
274}
275
276struct TraceEntry {
280 handler: Arc<dyn TraceHandler>,
281 filter: TraceFilter,
282}
283
284pub struct TraceRegistry {
289 entries: Vec<TraceEntry>,
290}
291
292impl TraceRegistry {
293 pub fn new() -> Self {
295 Self {
296 entries: Vec::new(),
297 }
298 }
299
300 pub fn register(&mut self, handler: Arc<dyn TraceHandler>, filter: TraceFilter) {
302 self.entries.push(TraceEntry { handler, filter });
303 }
304
305 pub async fn dispatch(
307 &self,
308 trace: &CallTrace,
309 ctx: &IndexContext,
310 ) -> Result<(), IndexerError> {
311 for entry in &self.entries {
312 if entry.filter.matches(trace) {
313 entry.handler.handle_trace(trace, ctx).await.map_err(|e| {
314 IndexerError::Handler {
315 handler: entry.handler.name().to_string(),
316 reason: e.to_string(),
317 }
318 })?;
319 }
320 }
321 Ok(())
322 }
323
324 pub async fn dispatch_batch(
326 &self,
327 traces: &[CallTrace],
328 ctx: &IndexContext,
329 ) -> Result<(), IndexerError> {
330 for trace in traces {
331 self.dispatch(trace, ctx).await?;
332 }
333 Ok(())
334 }
335
336 pub fn handler_count(&self) -> usize {
338 self.entries.len()
339 }
340}
341
342impl Default for TraceRegistry {
343 fn default() -> Self {
344 Self::new()
345 }
346}
347
348pub fn parse_geth_traces(
365 json: &serde_json::Value,
366 block_number: u64,
367) -> Result<Vec<CallTrace>, IndexerError> {
368 let results = json
369 .as_array()
370 .ok_or_else(|| IndexerError::Rpc("expected array of trace results".into()))?;
371
372 let mut traces = Vec::new();
373
374 for (tx_index, entry) in results.iter().enumerate() {
375 let tx_hash = entry
377 .get("txHash")
378 .and_then(|v| v.as_str())
379 .unwrap_or("")
380 .to_string();
381
382 let result = entry.get("result").unwrap_or(entry);
383
384 let mut trace_index: u32 = 0;
385 flatten_geth_call(
386 result,
387 block_number,
388 &tx_hash,
389 tx_index as u32,
390 0, false, &mut trace_index,
393 &mut traces,
394 );
395 }
396
397 Ok(traces)
398}
399
400#[allow(clippy::too_many_arguments)]
402fn flatten_geth_call(
403 node: &serde_json::Value,
404 block_number: u64,
405 tx_hash: &str,
406 tx_index: u32,
407 depth: u32,
408 parent_reverted: bool,
409 trace_index: &mut u32,
410 out: &mut Vec<CallTrace>,
411) {
412 let call_type_str = node.get("type").and_then(|v| v.as_str()).unwrap_or("CALL");
413
414 let call_type = CallType::from_geth(call_type_str).unwrap_or(CallType::Call);
415
416 let from = node
417 .get("from")
418 .and_then(|v| v.as_str())
419 .unwrap_or("")
420 .to_lowercase();
421
422 let to = node
423 .get("to")
424 .and_then(|v| v.as_str())
425 .unwrap_or("")
426 .to_lowercase();
427
428 let value = node
429 .get("value")
430 .and_then(|v| v.as_str())
431 .unwrap_or("0x0")
432 .to_string();
433
434 let gas_used = node
435 .get("gasUsed")
436 .and_then(|v| v.as_str())
437 .and_then(|s| u64::from_str_radix(s.strip_prefix("0x").unwrap_or(s), 16).ok())
438 .unwrap_or(0);
439
440 let input = node
441 .get("input")
442 .and_then(|v| v.as_str())
443 .unwrap_or("0x")
444 .to_string();
445
446 let output = node
447 .get("output")
448 .and_then(|v| v.as_str())
449 .unwrap_or("0x")
450 .to_string();
451
452 let error = node.get("error").and_then(|v| v.as_str()).map(String::from);
453 let reverted = parent_reverted || error.is_some();
454
455 let function_selector = CallTrace::extract_selector(&input);
456
457 let current_index = *trace_index;
458 *trace_index += 1;
459
460 out.push(CallTrace {
461 call_type,
462 from,
463 to,
464 value,
465 gas_used,
466 input,
467 output,
468 function_selector,
469 depth,
470 block_number,
471 tx_hash: tx_hash.to_string(),
472 tx_index,
473 trace_index: current_index,
474 error,
475 reverted,
476 });
477
478 if let Some(calls) = node.get("calls").and_then(|v| v.as_array()) {
480 for child in calls {
481 flatten_geth_call(
482 child,
483 block_number,
484 tx_hash,
485 tx_index,
486 depth + 1,
487 reverted,
488 trace_index,
489 out,
490 );
491 }
492 }
493}
494
495pub fn parse_parity_traces(
513 json: &serde_json::Value,
514 block_number: u64,
515) -> Result<Vec<CallTrace>, IndexerError> {
516 let traces_arr = json
517 .as_array()
518 .ok_or_else(|| IndexerError::Rpc("expected array of parity traces".into()))?;
519
520 let mut traces = Vec::new();
521
522 for (i, entry) in traces_arr.iter().enumerate() {
523 let action = entry.get("action").unwrap_or(entry);
524
525 let trace_type = entry.get("type").and_then(|v| v.as_str()).unwrap_or("call");
526
527 let call_type = CallType::from_parity(trace_type).unwrap_or(CallType::Call);
528
529 let from = action
530 .get("from")
531 .and_then(|v| v.as_str())
532 .unwrap_or("")
533 .to_lowercase();
534
535 let to = action
536 .get("to")
537 .and_then(|v| v.as_str())
538 .unwrap_or("")
539 .to_lowercase();
540
541 let value = action
542 .get("value")
543 .and_then(|v| v.as_str())
544 .unwrap_or("0x0")
545 .to_string();
546
547 let gas_used = entry
548 .get("result")
549 .and_then(|r| r.get("gasUsed"))
550 .and_then(|v| v.as_str())
551 .and_then(|s| u64::from_str_radix(s.strip_prefix("0x").unwrap_or(s), 16).ok())
552 .unwrap_or(0);
553
554 let input = action
555 .get("input")
556 .and_then(|v| v.as_str())
557 .unwrap_or("0x")
558 .to_string();
559
560 let output = entry
561 .get("result")
562 .and_then(|r| r.get("output"))
563 .and_then(|v| v.as_str())
564 .unwrap_or("0x")
565 .to_string();
566
567 let tx_hash = entry
568 .get("transactionHash")
569 .and_then(|v| v.as_str())
570 .unwrap_or("")
571 .to_string();
572
573 let tx_index = entry
574 .get("transactionPosition")
575 .and_then(|v| v.as_u64())
576 .unwrap_or(0) as u32;
577
578 let depth = entry
580 .get("traceAddress")
581 .and_then(|v| v.as_array())
582 .map(|a| a.len() as u32)
583 .unwrap_or(0);
584
585 let error_str = entry
586 .get("error")
587 .and_then(|v| v.as_str())
588 .map(String::from);
589 let reverted = error_str.is_some();
590
591 let function_selector = CallTrace::extract_selector(&input);
592
593 traces.push(CallTrace {
594 call_type,
595 from,
596 to,
597 value,
598 gas_used,
599 input,
600 output,
601 function_selector,
602 depth,
603 block_number,
604 tx_hash,
605 tx_index,
606 trace_index: i as u32,
607 error: error_str,
608 reverted,
609 });
610 }
611
612 Ok(traces)
613}
614
615#[cfg(test)]
618mod tests {
619 use super::*;
620 use std::sync::atomic::{AtomicU32, Ordering};
621
622 fn dummy_ctx() -> IndexContext {
623 IndexContext {
624 block: crate::types::BlockSummary {
625 number: 1,
626 hash: "0xa".into(),
627 parent_hash: "0x0".into(),
628 timestamp: 0,
629 tx_count: 0,
630 },
631 phase: crate::types::IndexPhase::Backfill,
632 chain: "ethereum".into(),
633 }
634 }
635
636 fn make_trace(
637 call_type: CallType,
638 from: &str,
639 to: &str,
640 selector: Option<&str>,
641 depth: u32,
642 reverted: bool,
643 ) -> CallTrace {
644 let input = match selector {
645 Some(sel) => format!("{}0000000000000000", sel),
646 None => "0x".to_string(),
647 };
648 let function_selector = CallTrace::extract_selector(&input);
649 CallTrace {
650 call_type,
651 from: from.to_lowercase(),
652 to: to.to_lowercase(),
653 value: "0x0".into(),
654 gas_used: 21000,
655 input,
656 output: "0x".into(),
657 function_selector,
658 depth,
659 block_number: 100,
660 tx_hash: "0xtxhash".into(),
661 tx_index: 0,
662 trace_index: 0,
663 error: if reverted {
664 Some("execution reverted".into())
665 } else {
666 None
667 },
668 reverted,
669 }
670 }
671
672 #[test]
675 fn call_type_from_geth() {
676 assert_eq!(CallType::from_geth("CALL"), Some(CallType::Call));
677 assert_eq!(
678 CallType::from_geth("DELEGATECALL"),
679 Some(CallType::DelegateCall)
680 );
681 assert_eq!(
682 CallType::from_geth("STATICCALL"),
683 Some(CallType::StaticCall)
684 );
685 assert_eq!(CallType::from_geth("CREATE"), Some(CallType::Create));
686 assert_eq!(CallType::from_geth("CREATE2"), Some(CallType::Create2));
687 assert_eq!(
688 CallType::from_geth("SELFDESTRUCT"),
689 Some(CallType::SelfDestruct)
690 );
691 assert_eq!(CallType::from_geth("UNKNOWN"), None);
692 }
693
694 #[test]
695 fn call_type_from_parity() {
696 assert_eq!(CallType::from_parity("call"), Some(CallType::Call));
697 assert_eq!(
698 CallType::from_parity("delegatecall"),
699 Some(CallType::DelegateCall)
700 );
701 assert_eq!(
702 CallType::from_parity("suicide"),
703 Some(CallType::SelfDestruct)
704 );
705 assert_eq!(
706 CallType::from_parity("selfdestruct"),
707 Some(CallType::SelfDestruct)
708 );
709 assert_eq!(CallType::from_parity("create"), Some(CallType::Create));
710 }
711
712 #[test]
715 fn function_selector_extraction() {
716 assert_eq!(
717 CallTrace::extract_selector("0xa9059cbb0000000000000000000000001234"),
718 Some("0xa9059cbb".into())
719 );
720 assert_eq!(CallTrace::extract_selector("0x"), None);
721 assert_eq!(CallTrace::extract_selector("0xabcd"), None); assert_eq!(
723 CallTrace::extract_selector("0xA9059CBB"),
724 Some("0xa9059cbb".into()) );
726 }
727
728 #[test]
731 fn filter_matches_all_by_default() {
732 let filter = TraceFilter::new();
733 let trace = make_trace(
734 CallType::Call,
735 "0xaaa",
736 "0xbbb",
737 Some("0xa9059cbb"),
738 0,
739 false,
740 );
741 assert!(filter.matches(&trace));
742 }
743
744 #[test]
745 fn filter_by_address() {
746 let filter = TraceFilter::new().with_address("0xaaa");
747
748 let t1 = make_trace(
750 CallType::Call,
751 "0xaaa",
752 "0xbbb",
753 Some("0xa9059cbb"),
754 0,
755 false,
756 );
757 assert!(filter.matches(&t1));
758
759 let t2 = make_trace(
761 CallType::Call,
762 "0xbbb",
763 "0xaaa",
764 Some("0xa9059cbb"),
765 0,
766 false,
767 );
768 assert!(filter.matches(&t2));
769
770 let t3 = make_trace(
772 CallType::Call,
773 "0xbbb",
774 "0xccc",
775 Some("0xa9059cbb"),
776 0,
777 false,
778 );
779 assert!(!filter.matches(&t3));
780 }
781
782 #[test]
783 fn filter_by_selector() {
784 let filter = TraceFilter::new().with_selector("0xa9059cbb");
785
786 let t1 = make_trace(
787 CallType::Call,
788 "0xaaa",
789 "0xbbb",
790 Some("0xa9059cbb"),
791 0,
792 false,
793 );
794 assert!(filter.matches(&t1));
795
796 let t2 = make_trace(
797 CallType::Call,
798 "0xaaa",
799 "0xbbb",
800 Some("0x12345678"),
801 0,
802 false,
803 );
804 assert!(!filter.matches(&t2));
805
806 let t3 = make_trace(CallType::Call, "0xaaa", "0xbbb", None, 0, false);
808 assert!(!filter.matches(&t3));
809 }
810
811 #[test]
812 fn filter_by_call_type() {
813 let filter = TraceFilter::new().with_call_type(CallType::Create);
814
815 let t1 = make_trace(CallType::Create, "0xaaa", "0xbbb", None, 0, false);
816 assert!(filter.matches(&t1));
817
818 let t2 = make_trace(CallType::Call, "0xaaa", "0xbbb", None, 0, false);
819 assert!(!filter.matches(&t2));
820 }
821
822 #[test]
823 fn filter_exclude_reverted() {
824 let filter = TraceFilter::new().exclude_reverted(true);
825
826 let t1 = make_trace(CallType::Call, "0xaaa", "0xbbb", None, 0, false);
827 assert!(filter.matches(&t1));
828
829 let t2 = make_trace(CallType::Call, "0xaaa", "0xbbb", None, 0, true);
830 assert!(!filter.matches(&t2));
831 }
832
833 #[test]
834 fn filter_by_depth() {
835 let filter = TraceFilter::new().min_depth(1).max_depth(3);
836
837 let t0 = make_trace(CallType::Call, "0xaaa", "0xbbb", None, 0, false);
838 assert!(!filter.matches(&t0)); let t1 = make_trace(CallType::Call, "0xaaa", "0xbbb", None, 1, false);
841 assert!(filter.matches(&t1));
842
843 let t3 = make_trace(CallType::Call, "0xaaa", "0xbbb", None, 3, false);
844 assert!(filter.matches(&t3));
845
846 let t4 = make_trace(CallType::Call, "0xaaa", "0xbbb", None, 4, false);
847 assert!(!filter.matches(&t4)); }
849
850 struct CountingHandler {
853 count: Arc<AtomicU32>,
854 handler_name: String,
855 }
856
857 #[async_trait]
858 impl TraceHandler for CountingHandler {
859 async fn handle_trace(
860 &self,
861 _trace: &CallTrace,
862 _ctx: &IndexContext,
863 ) -> Result<(), IndexerError> {
864 self.count.fetch_add(1, Ordering::Relaxed);
865 Ok(())
866 }
867 fn name(&self) -> &str {
868 &self.handler_name
869 }
870 }
871
872 #[tokio::test]
873 async fn dispatch_to_matching_handler() {
874 let count = Arc::new(AtomicU32::new(0));
875 let handler = Arc::new(CountingHandler {
876 count: count.clone(),
877 handler_name: "test_handler".into(),
878 });
879
880 let mut registry = TraceRegistry::new();
881 registry.register(handler, TraceFilter::new().with_call_type(CallType::Create));
882
883 let ctx = dummy_ctx();
884
885 let t1 = make_trace(CallType::Create, "0xaaa", "0xbbb", None, 0, false);
887 registry.dispatch(&t1, &ctx).await.unwrap();
888
889 let t2 = make_trace(CallType::Call, "0xaaa", "0xbbb", None, 0, false);
891 registry.dispatch(&t2, &ctx).await.unwrap();
892
893 assert_eq!(count.load(Ordering::Relaxed), 1);
894 }
895
896 #[tokio::test]
897 async fn dispatch_batch() {
898 let count = Arc::new(AtomicU32::new(0));
899 let handler = Arc::new(CountingHandler {
900 count: count.clone(),
901 handler_name: "batch_handler".into(),
902 });
903
904 let mut registry = TraceRegistry::new();
905 registry.register(handler, TraceFilter::new());
906
907 let ctx = dummy_ctx();
908 let traces = vec![
909 make_trace(CallType::Call, "0xaaa", "0xbbb", None, 0, false),
910 make_trace(CallType::Create, "0xaaa", "0xbbb", None, 0, false),
911 make_trace(CallType::DelegateCall, "0xaaa", "0xbbb", None, 0, false),
912 ];
913
914 registry.dispatch_batch(&traces, &ctx).await.unwrap();
915 assert_eq!(count.load(Ordering::Relaxed), 3);
916 }
917
918 #[test]
921 fn parse_geth_trace_basic() {
922 let json = serde_json::json!([
923 {
924 "txHash": "0xabc123",
925 "result": {
926 "type": "CALL",
927 "from": "0xSender",
928 "to": "0xReceiver",
929 "value": "0xde0b6b3a7640000",
930 "gasUsed": "0x5208",
931 "input": "0xa9059cbb0000000000000000000000001234",
932 "output": "0x0000000000000000000000000000000000000001",
933 "calls": [
934 {
935 "type": "DELEGATECALL",
936 "from": "0xReceiver",
937 "to": "0xImpl",
938 "value": "0x0",
939 "gasUsed": "0x1000",
940 "input": "0xa9059cbb0000",
941 "output": "0x01"
942 }
943 ]
944 }
945 }
946 ]);
947
948 let traces = parse_geth_traces(&json, 12345).unwrap();
949 assert_eq!(traces.len(), 2);
950
951 assert_eq!(traces[0].call_type, CallType::Call);
953 assert_eq!(traces[0].from, "0xsender");
954 assert_eq!(traces[0].to, "0xreceiver");
955 assert_eq!(traces[0].depth, 0);
956 assert_eq!(traces[0].block_number, 12345);
957 assert_eq!(traces[0].tx_hash, "0xabc123");
958 assert_eq!(traces[0].gas_used, 0x5208);
959 assert_eq!(traces[0].function_selector, Some("0xa9059cbb".into()));
960 assert!(!traces[0].reverted);
961 assert_eq!(traces[0].trace_index, 0);
962
963 assert_eq!(traces[1].call_type, CallType::DelegateCall);
965 assert_eq!(traces[1].depth, 1);
966 assert_eq!(traces[1].trace_index, 1);
967 }
968
969 #[test]
970 fn parse_geth_trace_with_error() {
971 let json = serde_json::json!([
972 {
973 "txHash": "0xfailed",
974 "result": {
975 "type": "CALL",
976 "from": "0xSender",
977 "to": "0xReceiver",
978 "value": "0x0",
979 "gasUsed": "0x5208",
980 "input": "0x",
981 "output": "0x",
982 "error": "execution reverted",
983 "calls": [
984 {
985 "type": "CALL",
986 "from": "0xReceiver",
987 "to": "0xInner",
988 "value": "0x0",
989 "gasUsed": "0x100",
990 "input": "0x",
991 "output": "0x"
992 }
993 ]
994 }
995 }
996 ]);
997
998 let traces = parse_geth_traces(&json, 100).unwrap();
999 assert_eq!(traces.len(), 2);
1000
1001 assert!(traces[0].reverted);
1003 assert_eq!(traces[0].error, Some("execution reverted".into()));
1004
1005 assert!(traces[1].reverted);
1007 }
1008
1009 #[test]
1012 fn parse_parity_trace_basic() {
1013 let json = serde_json::json!([
1014 {
1015 "action": {
1016 "from": "0xSender",
1017 "to": "0xReceiver",
1018 "value": "0xde0b6b3a7640000",
1019 "input": "0xa9059cbb0000000000000000000000001234"
1020 },
1021 "result": {
1022 "gasUsed": "0x5208",
1023 "output": "0x0001"
1024 },
1025 "transactionHash": "0xparity_tx",
1026 "transactionPosition": 0,
1027 "traceAddress": [],
1028 "type": "call"
1029 },
1030 {
1031 "action": {
1032 "from": "0xReceiver",
1033 "to": "0xInner",
1034 "value": "0x0",
1035 "input": "0x12345678aabbccdd"
1036 },
1037 "result": {
1038 "gasUsed": "0x1000",
1039 "output": "0x"
1040 },
1041 "transactionHash": "0xparity_tx",
1042 "transactionPosition": 0,
1043 "traceAddress": [0],
1044 "type": "call"
1045 }
1046 ]);
1047
1048 let traces = parse_parity_traces(&json, 999).unwrap();
1049 assert_eq!(traces.len(), 2);
1050
1051 assert_eq!(traces[0].call_type, CallType::Call);
1053 assert_eq!(traces[0].from, "0xsender");
1054 assert_eq!(traces[0].to, "0xreceiver");
1055 assert_eq!(traces[0].depth, 0);
1056 assert_eq!(traces[0].block_number, 999);
1057 assert_eq!(traces[0].tx_hash, "0xparity_tx");
1058 assert_eq!(traces[0].function_selector, Some("0xa9059cbb".into()));
1059
1060 assert_eq!(traces[1].depth, 1);
1062 assert_eq!(traces[1].function_selector, Some("0x12345678".into()));
1063 }
1064
1065 #[test]
1066 fn parse_parity_trace_create() {
1067 let json = serde_json::json!([
1068 {
1069 "action": {
1070 "from": "0xDeployer",
1071 "value": "0x0",
1072 "init": "0x6080604052"
1073 },
1074 "result": {
1075 "address": "0xNewContract",
1076 "gasUsed": "0x30000",
1077 "code": "0x6080"
1078 },
1079 "transactionHash": "0xcreate_tx",
1080 "transactionPosition": 1,
1081 "traceAddress": [],
1082 "type": "create"
1083 }
1084 ]);
1085
1086 let traces = parse_parity_traces(&json, 500).unwrap();
1087 assert_eq!(traces.len(), 1);
1088 assert_eq!(traces[0].call_type, CallType::Create);
1089 assert_eq!(traces[0].from, "0xdeployer");
1090 }
1091
1092 #[test]
1093 fn parse_parity_trace_with_error() {
1094 let json = serde_json::json!([
1095 {
1096 "action": {
1097 "from": "0xSender",
1098 "to": "0xReceiver",
1099 "value": "0x0",
1100 "input": "0x"
1101 },
1102 "transactionHash": "0xfail_tx",
1103 "transactionPosition": 0,
1104 "traceAddress": [],
1105 "type": "call",
1106 "error": "out of gas"
1107 }
1108 ]);
1109
1110 let traces = parse_parity_traces(&json, 200).unwrap();
1111 assert_eq!(traces.len(), 1);
1112 assert!(traces[0].reverted);
1113 assert_eq!(traces[0].error, Some("out of gas".into()));
1114 }
1115
1116 #[test]
1119 fn geth_trace_depth_tracking() {
1120 let json = serde_json::json!([
1121 {
1122 "txHash": "0xdeep",
1123 "result": {
1124 "type": "CALL",
1125 "from": "0xa",
1126 "to": "0xb",
1127 "value": "0x0",
1128 "gasUsed": "0x100",
1129 "input": "0x",
1130 "output": "0x",
1131 "calls": [
1132 {
1133 "type": "CALL",
1134 "from": "0xb",
1135 "to": "0xc",
1136 "value": "0x0",
1137 "gasUsed": "0x50",
1138 "input": "0x",
1139 "output": "0x",
1140 "calls": [
1141 {
1142 "type": "STATICCALL",
1143 "from": "0xc",
1144 "to": "0xd",
1145 "value": "0x0",
1146 "gasUsed": "0x20",
1147 "input": "0x",
1148 "output": "0x"
1149 }
1150 ]
1151 }
1152 ]
1153 }
1154 }
1155 ]);
1156
1157 let traces = parse_geth_traces(&json, 1).unwrap();
1158 assert_eq!(traces.len(), 3);
1159 assert_eq!(traces[0].depth, 0);
1160 assert_eq!(traces[1].depth, 1);
1161 assert_eq!(traces[2].depth, 2);
1162 assert_eq!(traces[2].call_type, CallType::StaticCall);
1163 }
1164
1165 #[test]
1168 fn combined_filter_all_criteria() {
1169 let filter = TraceFilter::new()
1170 .with_address("0xaaa")
1171 .with_call_type(CallType::Call)
1172 .with_selector("0xa9059cbb")
1173 .exclude_reverted(true);
1174
1175 let t1 = make_trace(
1177 CallType::Call,
1178 "0xaaa",
1179 "0xbbb",
1180 Some("0xa9059cbb"),
1181 0,
1182 false,
1183 );
1184 assert!(filter.matches(&t1));
1185
1186 let t2 = make_trace(
1188 CallType::Create,
1189 "0xaaa",
1190 "0xbbb",
1191 Some("0xa9059cbb"),
1192 0,
1193 false,
1194 );
1195 assert!(!filter.matches(&t2));
1196
1197 let t3 = make_trace(
1199 CallType::Call,
1200 "0xzzz",
1201 "0xbbb",
1202 Some("0xa9059cbb"),
1203 0,
1204 false,
1205 );
1206 assert!(!filter.matches(&t3));
1207
1208 let t4 = make_trace(
1210 CallType::Call,
1211 "0xaaa",
1212 "0xbbb",
1213 Some("0xa9059cbb"),
1214 0,
1215 true,
1216 );
1217 assert!(!filter.matches(&t4));
1218 }
1219}