hypersync_net_types/
trace.rs

1use crate::{
2    hypersync_net_types_capnp,
3    types::{AnyOf, Sighash},
4    CapnpBuilder, CapnpReader, Selection,
5};
6use anyhow::Context;
7use hypersync_format::{Address, FilterWrapper};
8use serde::{Deserialize, Serialize};
9
10pub type TraceSelection = Selection<TraceFilter>;
11
12impl From<TraceFilter> for AnyOf<TraceFilter> {
13    fn from(filter: TraceFilter) -> Self {
14        Self::new(filter)
15    }
16}
17
18#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq)]
19#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
20pub struct TraceFilter {
21    #[serde(default, skip_serializing_if = "Vec::is_empty")]
22    pub from: Vec<Address>,
23    #[serde(default, skip_serializing_if = "Option::is_none")]
24    pub from_filter: Option<FilterWrapper>,
25    #[serde(default, skip_serializing_if = "Vec::is_empty")]
26    pub to: Vec<Address>,
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub to_filter: Option<FilterWrapper>,
29    #[serde(default, skip_serializing_if = "Vec::is_empty")]
30    pub address: Vec<Address>,
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub address_filter: Option<FilterWrapper>,
33    #[serde(default, skip_serializing_if = "Vec::is_empty")]
34    pub call_type: Vec<String>,
35    #[serde(default, skip_serializing_if = "Vec::is_empty")]
36    pub reward_type: Vec<String>,
37    #[serde(default, skip_serializing_if = "Vec::is_empty")]
38    #[serde(rename = "type")]
39    pub type_: Vec<String>,
40    #[serde(default, skip_serializing_if = "Vec::is_empty")]
41    pub sighash: Vec<Sighash>,
42}
43
44impl TraceFilter {
45    /// Create a trace filter that matches any trace.
46    ///
47    /// This creates an empty filter with no constraints, which will match all traces.
48    /// You can then use the builder methods to add specific filtering criteria.
49    ///
50    /// # Examples
51    ///
52    /// ```
53    /// use hypersync_net_types::TraceFilter;
54    ///
55    /// // Create a filter that matches any trace
56    /// let filter = TraceFilter::all();
57    ///
58    /// // Chain with other filter methods
59    /// let filter = TraceFilter::all()
60    ///     .and_from(["0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567"])?;
61    /// # Ok::<(), anyhow::Error>(())
62    /// ```
63    pub fn all() -> Self {
64        Default::default()
65    }
66
67    /// Combine this filter with another using logical OR.
68    ///
69    /// Creates an `AnyOf` that matches traces satisfying either this filter or the other filter.
70    /// This allows for fluent chaining of multiple trace filters with OR semantics.
71    ///
72    /// # Arguments
73    /// * `other` - Another `TraceFilter` to combine with this one
74    ///
75    /// # Returns
76    /// An `AnyOf<TraceFilter>` that matches traces satisfying either filter
77    ///
78    /// # Examples
79    ///
80    /// ```
81    /// use hypersync_net_types::TraceFilter;
82    ///
83    /// // Match traces from specific callers OR with specific call types
84    /// let filter = TraceFilter::all()
85    ///     .and_from(["0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567"])?
86    ///     .or(
87    ///         TraceFilter::all()
88    ///             .and_call_type(["create", "create2"])
89    ///     );
90    /// # Ok::<(), anyhow::Error>(())
91    /// ```
92    pub fn or(self, other: Self) -> AnyOf<Self> {
93        AnyOf::new(self).or(other)
94    }
95
96    /// Filter traces by any of the provided "from" addresses.
97    ///
98    /// This method accepts any iterable of values that can be converted to `Address`.
99    /// Common input types include string slices, byte arrays, and `Address` objects.
100    /// The "from" address typically represents the caller or originator of the trace.
101    ///
102    /// # Arguments
103    /// * `addresses` - An iterable of addresses to filter by
104    ///
105    /// # Returns
106    /// * `Ok(Self)` - The updated filter on success
107    /// * `Err(anyhow::Error)` - If any address fails to convert
108    ///
109    /// # Examples
110    ///
111    /// ```
112    /// use hypersync_net_types::TraceFilter;
113    ///
114    /// // Filter by a single caller address
115    /// let filter = TraceFilter::all()
116    ///     .and_from(["0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567"])?;
117    ///
118    /// // Filter by multiple caller addresses
119    /// let filter = TraceFilter::all()
120    ///     .and_from([
121    ///         "0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567",
122    ///         "0xdac17f958d2ee523a2206206994597c13d831ec7",
123    ///     ])?;
124    ///
125    /// // Using byte arrays
126    /// let caller_address = [
127    ///     0xa0, 0xb8, 0x6a, 0x33, 0xe6, 0xc1, 0x1c, 0x8c, 0x0c, 0x5c,
128    ///     0x0b, 0x5e, 0x6a, 0xde, 0xe3, 0x0d, 0x1a, 0x23, 0x45, 0x67
129    /// ];
130    /// let filter = TraceFilter::all()
131    ///     .and_from([caller_address])?;
132    /// # Ok::<(), anyhow::Error>(())
133    /// ```
134    pub fn and_from<I, A>(mut self, addresses: I) -> anyhow::Result<Self>
135    where
136        I: IntoIterator<Item = A>,
137        A: TryInto<Address>,
138        A::Error: std::error::Error + Send + Sync + 'static,
139    {
140        let mut converted_addresses: Vec<Address> = Vec::new();
141        for (idx, address) in addresses.into_iter().enumerate() {
142            converted_addresses.push(
143                address
144                    .try_into()
145                    .with_context(|| format!("invalid from address at position {idx}"))?,
146            );
147        }
148        self.from = converted_addresses;
149        Ok(self)
150    }
151
152    /// Filter traces by any of the provided "to" addresses.
153    ///
154    /// This method accepts any iterable of values that can be converted to `Address`.
155    /// Common input types include string slices, byte arrays, and `Address` objects.
156    /// The "to" address typically represents the target or recipient of the trace.
157    ///
158    /// # Arguments
159    /// * `addresses` - An iterable of addresses to filter by
160    ///
161    /// # Returns
162    /// * `Ok(Self)` - The updated filter on success
163    /// * `Err(anyhow::Error)` - If any address fails to convert
164    ///
165    /// # Examples
166    ///
167    /// ```
168    /// use hypersync_net_types::TraceFilter;
169    ///
170    /// // Filter by a single target address
171    /// let filter = TraceFilter::all()
172    ///     .and_to(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?;
173    ///
174    /// // Filter by multiple target addresses
175    /// let filter = TraceFilter::all()
176    ///     .and_to([
177    ///         "0xdac17f958d2ee523a2206206994597c13d831ec7",
178    ///         "0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567",
179    ///     ])?;
180    ///
181    /// // Chain with from address filtering
182    /// let filter = TraceFilter::all()
183    ///     .and_from(["0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567"])?
184    ///     .and_to(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?;
185    /// # Ok::<(), anyhow::Error>(())
186    /// ```
187    pub fn and_to<I, A>(mut self, addresses: I) -> anyhow::Result<Self>
188    where
189        I: IntoIterator<Item = A>,
190        A: TryInto<Address>,
191        A::Error: std::error::Error + Send + Sync + 'static,
192    {
193        let mut converted_addresses: Vec<Address> = Vec::new();
194        for (idx, address) in addresses.into_iter().enumerate() {
195            converted_addresses.push(
196                address
197                    .try_into()
198                    .with_context(|| format!("invalid to address at position {idx}"))?,
199            );
200        }
201        self.to = converted_addresses;
202        Ok(self)
203    }
204
205    /// Filter traces by any of the provided contract addresses.
206    ///
207    /// This method accepts any iterable of values that can be converted to `Address`.
208    /// Common input types include string slices, byte arrays, and `Address` objects.
209    /// The address field typically represents the contract address involved in the trace.
210    ///
211    /// # Arguments
212    /// * `addresses` - An iterable of addresses to filter by
213    ///
214    /// # Returns
215    /// * `Ok(Self)` - The updated filter on success
216    /// * `Err(anyhow::Error)` - If any address fails to convert
217    ///
218    /// # Examples
219    ///
220    /// ```
221    /// use hypersync_net_types::TraceFilter;
222    ///
223    /// // Filter by a single contract address
224    /// let filter = TraceFilter::all()
225    ///     .and_address(["0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567"])?;
226    ///
227    /// // Filter by multiple contract addresses
228    /// let filter = TraceFilter::all()
229    ///     .and_address([
230    ///         "0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567",
231    ///         "0xdac17f958d2ee523a2206206994597c13d831ec7",
232    ///     ])?;
233    /// # Ok::<(), anyhow::Error>(())
234    /// ```
235    pub fn and_address<I, A>(mut self, addresses: I) -> anyhow::Result<Self>
236    where
237        I: IntoIterator<Item = A>,
238        A: TryInto<Address>,
239        A::Error: std::error::Error + Send + Sync + 'static,
240    {
241        let mut converted_addresses: Vec<Address> = Vec::new();
242        for (idx, address) in addresses.into_iter().enumerate() {
243            converted_addresses.push(
244                address
245                    .try_into()
246                    .with_context(|| format!("invalid address at position {idx}"))?,
247            );
248        }
249        self.address = converted_addresses;
250        Ok(self)
251    }
252
253    /// Filter traces by any of the provided call types.
254    ///
255    /// This method accepts any iterable of values that can be converted to `String`.
256    /// Common call types include "call", "staticcall", "delegatecall", "create", "create2", etc.
257    ///
258    /// # Arguments
259    /// * `call_types` - An iterable of call type strings to filter by
260    ///
261    /// # Examples
262    ///
263    /// ```
264    /// use hypersync_net_types::TraceFilter;
265    ///
266    /// // Filter by specific call types
267    /// let filter = TraceFilter::all()
268    ///     .and_call_type(["call", "delegatecall"]);
269    ///
270    /// // Filter by contract creation traces
271    /// let filter = TraceFilter::all()
272    ///     .and_call_type(["create", "create2"]);
273    ///
274    /// // Chain with address filtering
275    /// let filter = TraceFilter::all()
276    ///     .and_from(["0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567"])?
277    ///     .and_call_type(["call"]);
278    /// # Ok::<(), anyhow::Error>(())
279    /// ```
280    pub fn and_call_type<I, S>(mut self, call_types: I) -> Self
281    where
282        I: IntoIterator<Item = S>,
283        S: Into<String>,
284    {
285        self.call_type = call_types.into_iter().map(Into::into).collect();
286        self
287    }
288
289    /// Filter traces by any of the provided reward types.
290    ///
291    /// This method accepts any iterable of values that can be converted to `String`.
292    /// Common reward types include "block", "uncle", etc., typically used for mining rewards.
293    ///
294    /// # Arguments
295    /// * `reward_types` - An iterable of reward type strings to filter by
296    ///
297    /// # Examples
298    ///
299    /// ```
300    /// use hypersync_net_types::TraceFilter;
301    ///
302    /// // Filter by block rewards
303    /// let filter = TraceFilter::all()
304    ///     .and_reward_type(["block"]);
305    ///
306    /// // Filter by both block and uncle rewards
307    /// let filter = TraceFilter::all()
308    ///     .and_reward_type(["block", "uncle"]);
309    /// ```
310    pub fn and_reward_type<I, S>(mut self, reward_types: I) -> Self
311    where
312        I: IntoIterator<Item = S>,
313        S: Into<String>,
314    {
315        self.reward_type = reward_types.into_iter().map(Into::into).collect();
316        self
317    }
318
319    /// Filter traces by any of the provided trace types.
320    ///
321    /// This method accepts any iterable of values that can be converted to `String`.
322    /// Common trace types include "call", "create", "suicide", "reward", etc.
323    ///
324    /// # Arguments
325    /// * `types` - An iterable of trace type strings to filter by
326    ///
327    /// # Examples
328    ///
329    /// ```
330    /// use hypersync_net_types::TraceFilter;
331    ///
332    /// // Filter by call traces
333    /// let filter = TraceFilter::all()
334    ///     .and_type(["call"]);
335    ///
336    /// // Filter by multiple trace types
337    /// let filter = TraceFilter::all()
338    ///     .and_type(["call", "create", "reward"]);
339    /// ```
340    pub fn and_type<I, S>(mut self, types: I) -> Self
341    where
342        I: IntoIterator<Item = S>,
343        S: Into<String>,
344    {
345        self.type_ = types.into_iter().map(Into::into).collect();
346        self
347    }
348
349    /// Filter traces by any of the provided function signature hashes (sighashes).
350    ///
351    /// This method accepts any iterable of values that can be converted to `Sighash`.
352    /// Common input types include string slices, byte arrays, and `Sighash` objects.
353    /// Sighashes are the first 4 bytes of the keccak256 hash of a function signature.
354    ///
355    /// # Arguments
356    /// * `sighashes` - An iterable of sighash values to filter by
357    ///
358    /// # Returns
359    /// * `Ok(Self)` - The updated filter on success
360    /// * `Err(anyhow::Error)` - If any sighash fails to convert
361    ///
362    /// # Examples
363    ///
364    /// ```
365    /// use hypersync_net_types::TraceFilter;
366    ///
367    /// // Filter by transfer function signature
368    /// let transfer_sig = "0xa9059cbb"; // transfer(address,uint256)
369    /// let filter = TraceFilter::all()
370    ///     .and_sighash([transfer_sig])?;
371    ///
372    /// // Filter by multiple function signatures
373    /// let filter = TraceFilter::all()
374    ///     .and_sighash([
375    ///         "0xa9059cbb", // transfer(address,uint256)
376    ///         "0x095ea7b3", // approve(address,uint256)
377    ///     ])?;
378    ///
379    /// // Using byte arrays
380    /// let transfer_bytes = [0xa9, 0x05, 0x9c, 0xbb];
381    /// let filter = TraceFilter::all()
382    ///     .and_sighash([transfer_bytes])?;
383    /// # Ok::<(), anyhow::Error>(())
384    /// ```
385    pub fn and_sighash<I, S>(mut self, sighashes: I) -> anyhow::Result<Self>
386    where
387        I: IntoIterator<Item = S>,
388        S: TryInto<Sighash>,
389        S::Error: std::error::Error + Send + Sync + 'static,
390    {
391        let mut converted_sighashes: Vec<Sighash> = Vec::new();
392        for (idx, sighash) in sighashes.into_iter().enumerate() {
393            converted_sighashes.push(
394                sighash
395                    .try_into()
396                    .with_context(|| format!("invalid sighash at position {idx}"))?,
397            );
398        }
399        self.sighash = converted_sighashes;
400        Ok(self)
401    }
402}
403
404impl CapnpBuilder<hypersync_net_types_capnp::trace_filter::Owned> for TraceFilter {
405    fn populate_builder(
406        &self,
407        builder: &mut hypersync_net_types_capnp::trace_filter::Builder,
408    ) -> Result<(), capnp::Error> {
409        // Set from addresses
410        if !self.from.is_empty() {
411            let mut from_list = builder.reborrow().init_from(self.from.len() as u32);
412            for (i, addr) in self.from.iter().enumerate() {
413                from_list.set(i as u32, addr.as_slice());
414            }
415        }
416
417        // Set from filter
418        if let Some(filter) = &self.from_filter {
419            builder.reborrow().set_from_filter(filter.0.as_bytes());
420        }
421
422        // Set to addresses
423        if !self.to.is_empty() {
424            let mut to_list = builder.reborrow().init_to(self.to.len() as u32);
425            for (i, addr) in self.to.iter().enumerate() {
426                to_list.set(i as u32, addr.as_slice());
427            }
428        }
429
430        // Set to filter
431        if let Some(filter) = &self.to_filter {
432            builder.reborrow().set_to_filter(filter.0.as_bytes());
433        }
434
435        // Set addresses
436        if !self.address.is_empty() {
437            let mut addr_list = builder.reborrow().init_address(self.address.len() as u32);
438            for (i, addr) in self.address.iter().enumerate() {
439                addr_list.set(i as u32, addr.as_slice());
440            }
441        }
442
443        // Set address filter
444        if let Some(filter) = &self.address_filter {
445            builder.reborrow().set_address_filter(filter.0.as_bytes());
446        }
447
448        // Set call types
449        if !self.call_type.is_empty() {
450            let mut call_type_list = builder
451                .reborrow()
452                .init_call_type(self.call_type.len() as u32);
453            for (i, call_type) in self.call_type.iter().enumerate() {
454                call_type_list.set(i as u32, call_type);
455            }
456        }
457
458        // Set reward types
459        if !self.reward_type.is_empty() {
460            let mut reward_type_list = builder
461                .reborrow()
462                .init_reward_type(self.reward_type.len() as u32);
463            for (i, reward_type) in self.reward_type.iter().enumerate() {
464                reward_type_list.set(i as u32, reward_type);
465            }
466        }
467
468        // Set types
469        if !self.type_.is_empty() {
470            let mut type_list = builder.reborrow().init_type(self.type_.len() as u32);
471            for (i, type_) in self.type_.iter().enumerate() {
472                type_list.set(i as u32, type_);
473            }
474        }
475
476        // Set sighash
477        if !self.sighash.is_empty() {
478            let mut sighash_list = builder.reborrow().init_sighash(self.sighash.len() as u32);
479            for (i, sighash) in self.sighash.iter().enumerate() {
480                sighash_list.set(i as u32, sighash.as_slice());
481            }
482        }
483
484        Ok(())
485    }
486}
487
488impl CapnpReader<hypersync_net_types_capnp::trace_filter::Owned> for TraceFilter {
489    /// Deserialize TraceSelection from Cap'n Proto reader
490    fn from_reader(
491        reader: hypersync_net_types_capnp::trace_filter::Reader,
492    ) -> Result<Self, capnp::Error> {
493        let mut from = Vec::new();
494
495        // Parse from addresses
496        if reader.has_from() {
497            let from_list = reader.get_from()?;
498            for i in 0..from_list.len() {
499                let addr_data = from_list.get(i)?;
500                if addr_data.len() == 20 {
501                    let mut addr_bytes = [0u8; 20];
502                    addr_bytes.copy_from_slice(addr_data);
503                    from.push(Address::from(addr_bytes));
504                }
505            }
506        }
507
508        let mut from_filter = None;
509
510        // Parse from filter
511        if reader.has_from_filter() {
512            let filter_data = reader.get_from_filter()?;
513            let Ok(wrapper) = FilterWrapper::from_bytes(filter_data) else {
514                return Err(capnp::Error::failed("Invalid from filter".to_string()));
515            };
516            from_filter = Some(wrapper);
517        }
518
519        let mut to = Vec::new();
520
521        // Parse to addresses
522        if reader.has_to() {
523            let to_list = reader.get_to()?;
524            for i in 0..to_list.len() {
525                let addr_data = to_list.get(i)?;
526                if addr_data.len() == 20 {
527                    let mut addr_bytes = [0u8; 20];
528                    addr_bytes.copy_from_slice(addr_data);
529                    to.push(Address::from(addr_bytes));
530                }
531            }
532        }
533
534        let mut to_filter = None;
535
536        // Parse to filter
537        if reader.has_to_filter() {
538            let filter_data = reader.get_to_filter()?;
539            let Ok(wrapper) = FilterWrapper::from_bytes(filter_data) else {
540                return Err(capnp::Error::failed("Invalid to filter".to_string()));
541            };
542            to_filter = Some(wrapper);
543        }
544
545        let mut address = Vec::new();
546
547        // Parse addresses
548        if reader.has_address() {
549            let addr_list = reader.get_address()?;
550            for i in 0..addr_list.len() {
551                let addr_data = addr_list.get(i)?;
552                if addr_data.len() == 20 {
553                    let mut addr_bytes = [0u8; 20];
554                    addr_bytes.copy_from_slice(addr_data);
555                    address.push(Address::from(addr_bytes));
556                }
557            }
558        }
559
560        let mut address_filter = None;
561
562        // Parse address filter
563        if reader.has_address_filter() {
564            let filter_data = reader.get_address_filter()?;
565            let Ok(wrapper) = FilterWrapper::from_bytes(filter_data) else {
566                return Err(capnp::Error::failed("Invalid address filter".to_string()));
567            };
568            address_filter = Some(wrapper);
569        }
570
571        let mut call_type = Vec::new();
572
573        // Parse call types
574        if reader.has_call_type() {
575            let call_type_list = reader.get_call_type()?;
576            for i in 0..call_type_list.len() {
577                let call_type_val = call_type_list.get(i)?;
578                call_type.push(call_type_val.to_string()?);
579            }
580        }
581
582        let mut reward_type = Vec::new();
583        // Parse reward types
584        if reader.has_reward_type() {
585            let reward_type_list = reader.get_reward_type()?;
586            for i in 0..reward_type_list.len() {
587                let reward_type_val = reward_type_list.get(i)?;
588                reward_type.push(reward_type_val.to_string()?);
589            }
590        }
591
592        let mut type_ = Vec::new();
593
594        // Parse types
595        if reader.has_type() {
596            let type_list = reader.get_type()?;
597            for i in 0..type_list.len() {
598                let type_val = type_list.get(i)?;
599                type_.push(type_val.to_string()?);
600            }
601        }
602
603        let mut sighash = Vec::new();
604
605        // Parse sighash
606        if reader.has_sighash() {
607            let sighash_list = reader.get_sighash()?;
608            for i in 0..sighash_list.len() {
609                let sighash_data = sighash_list.get(i)?;
610                if sighash_data.len() == 4 {
611                    let mut sighash_bytes = [0u8; 4];
612                    sighash_bytes.copy_from_slice(sighash_data);
613                    sighash.push(Sighash::from(sighash_bytes));
614                }
615            }
616        }
617
618        Ok(Self {
619            from,
620            from_filter,
621            to,
622            to_filter,
623            address,
624            address_filter,
625            call_type,
626            reward_type,
627            type_,
628            sighash,
629        })
630    }
631}
632
633#[derive(
634    Debug,
635    Clone,
636    Copy,
637    Serialize,
638    Deserialize,
639    PartialEq,
640    Eq,
641    schemars::JsonSchema,
642    strum_macros::EnumIter,
643    strum_macros::AsRefStr,
644    strum_macros::Display,
645    strum_macros::EnumString,
646)]
647#[serde(rename_all = "snake_case")]
648#[strum(serialize_all = "snake_case")]
649#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
650pub enum TraceField {
651    // Core trace fields
652    TransactionHash,
653    BlockHash,
654    BlockNumber,
655    TransactionPosition,
656    Type,
657    Error,
658
659    // Address fields
660    From,
661    To,
662    Author,
663
664    // Gas fields
665    Gas,
666    GasUsed,
667
668    // Additional trace fields from Arrow schema
669    ActionAddress,
670    Address,
671    Balance,
672    CallType,
673    Code,
674    Init,
675    Input,
676    Output,
677    RefundAddress,
678    RewardType,
679    Sighash,
680    Subtraces,
681    TraceAddress,
682    Value,
683}
684
685impl Ord for TraceField {
686    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
687        self.as_ref().cmp(other.as_ref())
688    }
689}
690
691impl PartialOrd for TraceField {
692    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
693        Some(self.cmp(other))
694    }
695}
696
697impl TraceField {
698    pub fn all() -> std::collections::BTreeSet<Self> {
699        use strum::IntoEnumIterator;
700        Self::iter().collect()
701    }
702
703    /// Convert TraceField to Cap'n Proto enum
704    pub fn to_capnp(&self) -> crate::hypersync_net_types_capnp::TraceField {
705        match self {
706            TraceField::TransactionHash => {
707                crate::hypersync_net_types_capnp::TraceField::TransactionHash
708            }
709            TraceField::BlockHash => crate::hypersync_net_types_capnp::TraceField::BlockHash,
710            TraceField::BlockNumber => crate::hypersync_net_types_capnp::TraceField::BlockNumber,
711            TraceField::TransactionPosition => {
712                crate::hypersync_net_types_capnp::TraceField::TransactionPosition
713            }
714            TraceField::Type => crate::hypersync_net_types_capnp::TraceField::Type,
715            TraceField::Error => crate::hypersync_net_types_capnp::TraceField::Error,
716            TraceField::From => crate::hypersync_net_types_capnp::TraceField::From,
717            TraceField::To => crate::hypersync_net_types_capnp::TraceField::To,
718            TraceField::Author => crate::hypersync_net_types_capnp::TraceField::Author,
719            TraceField::Gas => crate::hypersync_net_types_capnp::TraceField::Gas,
720            TraceField::GasUsed => crate::hypersync_net_types_capnp::TraceField::GasUsed,
721            TraceField::ActionAddress => {
722                crate::hypersync_net_types_capnp::TraceField::ActionAddress
723            }
724            TraceField::Address => crate::hypersync_net_types_capnp::TraceField::Address,
725            TraceField::Balance => crate::hypersync_net_types_capnp::TraceField::Balance,
726            TraceField::CallType => crate::hypersync_net_types_capnp::TraceField::CallType,
727            TraceField::Code => crate::hypersync_net_types_capnp::TraceField::Code,
728            TraceField::Init => crate::hypersync_net_types_capnp::TraceField::Init,
729            TraceField::Input => crate::hypersync_net_types_capnp::TraceField::Input,
730            TraceField::Output => crate::hypersync_net_types_capnp::TraceField::Output,
731            TraceField::RefundAddress => {
732                crate::hypersync_net_types_capnp::TraceField::RefundAddress
733            }
734            TraceField::RewardType => crate::hypersync_net_types_capnp::TraceField::RewardType,
735            TraceField::Sighash => crate::hypersync_net_types_capnp::TraceField::Sighash,
736            TraceField::Subtraces => crate::hypersync_net_types_capnp::TraceField::Subtraces,
737            TraceField::TraceAddress => crate::hypersync_net_types_capnp::TraceField::TraceAddress,
738            TraceField::Value => crate::hypersync_net_types_capnp::TraceField::Value,
739        }
740    }
741
742    /// Convert Cap'n Proto enum to TraceField
743    pub fn from_capnp(field: crate::hypersync_net_types_capnp::TraceField) -> Self {
744        match field {
745            crate::hypersync_net_types_capnp::TraceField::TransactionHash => {
746                TraceField::TransactionHash
747            }
748            crate::hypersync_net_types_capnp::TraceField::BlockHash => TraceField::BlockHash,
749            crate::hypersync_net_types_capnp::TraceField::BlockNumber => TraceField::BlockNumber,
750            crate::hypersync_net_types_capnp::TraceField::TransactionPosition => {
751                TraceField::TransactionPosition
752            }
753            crate::hypersync_net_types_capnp::TraceField::Type => TraceField::Type,
754            crate::hypersync_net_types_capnp::TraceField::Error => TraceField::Error,
755            crate::hypersync_net_types_capnp::TraceField::From => TraceField::From,
756            crate::hypersync_net_types_capnp::TraceField::To => TraceField::To,
757            crate::hypersync_net_types_capnp::TraceField::Author => TraceField::Author,
758            crate::hypersync_net_types_capnp::TraceField::Gas => TraceField::Gas,
759            crate::hypersync_net_types_capnp::TraceField::GasUsed => TraceField::GasUsed,
760            crate::hypersync_net_types_capnp::TraceField::ActionAddress => {
761                TraceField::ActionAddress
762            }
763            crate::hypersync_net_types_capnp::TraceField::Address => TraceField::Address,
764            crate::hypersync_net_types_capnp::TraceField::Balance => TraceField::Balance,
765            crate::hypersync_net_types_capnp::TraceField::CallType => TraceField::CallType,
766            crate::hypersync_net_types_capnp::TraceField::Code => TraceField::Code,
767            crate::hypersync_net_types_capnp::TraceField::Init => TraceField::Init,
768            crate::hypersync_net_types_capnp::TraceField::Input => TraceField::Input,
769            crate::hypersync_net_types_capnp::TraceField::Output => TraceField::Output,
770            crate::hypersync_net_types_capnp::TraceField::RefundAddress => {
771                TraceField::RefundAddress
772            }
773            crate::hypersync_net_types_capnp::TraceField::RewardType => TraceField::RewardType,
774            crate::hypersync_net_types_capnp::TraceField::Sighash => TraceField::Sighash,
775            crate::hypersync_net_types_capnp::TraceField::Subtraces => TraceField::Subtraces,
776            crate::hypersync_net_types_capnp::TraceField::TraceAddress => TraceField::TraceAddress,
777            crate::hypersync_net_types_capnp::TraceField::Value => TraceField::Value,
778        }
779    }
780}
781
782#[cfg(test)]
783mod tests {
784    use hypersync_format::Hex;
785
786    use super::*;
787    use crate::{query::tests::test_query_serde, Query};
788
789    #[test]
790    fn test_all_fields_in_schema() {
791        let schema = hypersync_schema::trace();
792        let schema_fields = schema
793            .fields
794            .iter()
795            .map(|f| f.name.clone())
796            .collect::<std::collections::BTreeSet<_>>();
797        let all_fields = TraceField::all()
798            .into_iter()
799            .map(|f| f.as_ref().to_string())
800            .collect::<std::collections::BTreeSet<_>>();
801        assert_eq!(schema_fields, all_fields);
802    }
803
804    #[test]
805    fn test_serde_matches_strum() {
806        for field in TraceField::all() {
807            let serialized = serde_json::to_string(&field).unwrap();
808            let strum = serde_json::to_string(&field.as_ref()).unwrap();
809            assert_eq!(serialized, strum, "strum value should be the same as serde");
810        }
811    }
812
813    #[test]
814    fn test_trace_filter_serde_with_defaults() {
815        let trace_filter = TraceSelection::default();
816        let query = Query::new()
817            .where_traces(trace_filter)
818            .select_trace_fields(TraceField::all());
819
820        test_query_serde(query, "trace selection with defaults");
821    }
822
823    #[test]
824    fn test_trace_filter_serde_with_full_values() {
825        let trace_filter = TraceFilter {
826            from: vec![Address::decode_hex("0xdadB0d80178819F2319190D340ce9A924f783711").unwrap()],
827            from_filter: Some(FilterWrapper::new(16, 1)),
828            to: vec![Address::decode_hex("0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6").unwrap()],
829            to_filter: Some(FilterWrapper::new(16, 1)),
830            address: vec![
831                Address::decode_hex("0x1234567890123456789012345678901234567890").unwrap(),
832            ],
833            address_filter: Some(FilterWrapper::new(16, 1)),
834            call_type: vec!["call".to_string(), "create".to_string()],
835            reward_type: vec!["block".to_string(), "uncle".to_string()],
836            type_: vec!["call".to_string(), "create".to_string()],
837            sighash: vec![Sighash::from([0x12, 0x34, 0x56, 0x78])],
838        };
839        let query = Query::new()
840            .where_traces(trace_filter)
841            .select_trace_fields(TraceField::all());
842
843        test_query_serde(query, "trace selection with full values");
844    }
845}