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