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    pub const fn is_nullable(&self) -> bool {
704        match self {
705            TraceField::From
706            | TraceField::To
707            | TraceField::CallType
708            | TraceField::Gas
709            | TraceField::Input
710            | TraceField::Init
711            | TraceField::Value
712            | TraceField::Author
713            | TraceField::RewardType
714            | TraceField::Address
715            | TraceField::Code
716            | TraceField::GasUsed
717            | TraceField::Output
718            | TraceField::Subtraces
719            | TraceField::TraceAddress
720            | TraceField::TransactionHash
721            | TraceField::TransactionPosition
722            | TraceField::Type
723            | TraceField::Error
724            | TraceField::Sighash
725            | TraceField::ActionAddress
726            | TraceField::Balance
727            | TraceField::RefundAddress => true,
728            TraceField::BlockHash | TraceField::BlockNumber => false,
729        }
730    }
731
732    /// Convert TraceField to Cap'n Proto enum
733    pub fn to_capnp(&self) -> crate::hypersync_net_types_capnp::TraceField {
734        match self {
735            TraceField::TransactionHash => {
736                crate::hypersync_net_types_capnp::TraceField::TransactionHash
737            }
738            TraceField::BlockHash => crate::hypersync_net_types_capnp::TraceField::BlockHash,
739            TraceField::BlockNumber => crate::hypersync_net_types_capnp::TraceField::BlockNumber,
740            TraceField::TransactionPosition => {
741                crate::hypersync_net_types_capnp::TraceField::TransactionPosition
742            }
743            TraceField::Type => crate::hypersync_net_types_capnp::TraceField::Type,
744            TraceField::Error => crate::hypersync_net_types_capnp::TraceField::Error,
745            TraceField::From => crate::hypersync_net_types_capnp::TraceField::From,
746            TraceField::To => crate::hypersync_net_types_capnp::TraceField::To,
747            TraceField::Author => crate::hypersync_net_types_capnp::TraceField::Author,
748            TraceField::Gas => crate::hypersync_net_types_capnp::TraceField::Gas,
749            TraceField::GasUsed => crate::hypersync_net_types_capnp::TraceField::GasUsed,
750            TraceField::ActionAddress => {
751                crate::hypersync_net_types_capnp::TraceField::ActionAddress
752            }
753            TraceField::Address => crate::hypersync_net_types_capnp::TraceField::Address,
754            TraceField::Balance => crate::hypersync_net_types_capnp::TraceField::Balance,
755            TraceField::CallType => crate::hypersync_net_types_capnp::TraceField::CallType,
756            TraceField::Code => crate::hypersync_net_types_capnp::TraceField::Code,
757            TraceField::Init => crate::hypersync_net_types_capnp::TraceField::Init,
758            TraceField::Input => crate::hypersync_net_types_capnp::TraceField::Input,
759            TraceField::Output => crate::hypersync_net_types_capnp::TraceField::Output,
760            TraceField::RefundAddress => {
761                crate::hypersync_net_types_capnp::TraceField::RefundAddress
762            }
763            TraceField::RewardType => crate::hypersync_net_types_capnp::TraceField::RewardType,
764            TraceField::Sighash => crate::hypersync_net_types_capnp::TraceField::Sighash,
765            TraceField::Subtraces => crate::hypersync_net_types_capnp::TraceField::Subtraces,
766            TraceField::TraceAddress => crate::hypersync_net_types_capnp::TraceField::TraceAddress,
767            TraceField::Value => crate::hypersync_net_types_capnp::TraceField::Value,
768        }
769    }
770
771    /// Convert Cap'n Proto enum to TraceField
772    pub fn from_capnp(field: crate::hypersync_net_types_capnp::TraceField) -> Self {
773        match field {
774            crate::hypersync_net_types_capnp::TraceField::TransactionHash => {
775                TraceField::TransactionHash
776            }
777            crate::hypersync_net_types_capnp::TraceField::BlockHash => TraceField::BlockHash,
778            crate::hypersync_net_types_capnp::TraceField::BlockNumber => TraceField::BlockNumber,
779            crate::hypersync_net_types_capnp::TraceField::TransactionPosition => {
780                TraceField::TransactionPosition
781            }
782            crate::hypersync_net_types_capnp::TraceField::Type => TraceField::Type,
783            crate::hypersync_net_types_capnp::TraceField::Error => TraceField::Error,
784            crate::hypersync_net_types_capnp::TraceField::From => TraceField::From,
785            crate::hypersync_net_types_capnp::TraceField::To => TraceField::To,
786            crate::hypersync_net_types_capnp::TraceField::Author => TraceField::Author,
787            crate::hypersync_net_types_capnp::TraceField::Gas => TraceField::Gas,
788            crate::hypersync_net_types_capnp::TraceField::GasUsed => TraceField::GasUsed,
789            crate::hypersync_net_types_capnp::TraceField::ActionAddress => {
790                TraceField::ActionAddress
791            }
792            crate::hypersync_net_types_capnp::TraceField::Address => TraceField::Address,
793            crate::hypersync_net_types_capnp::TraceField::Balance => TraceField::Balance,
794            crate::hypersync_net_types_capnp::TraceField::CallType => TraceField::CallType,
795            crate::hypersync_net_types_capnp::TraceField::Code => TraceField::Code,
796            crate::hypersync_net_types_capnp::TraceField::Init => TraceField::Init,
797            crate::hypersync_net_types_capnp::TraceField::Input => TraceField::Input,
798            crate::hypersync_net_types_capnp::TraceField::Output => TraceField::Output,
799            crate::hypersync_net_types_capnp::TraceField::RefundAddress => {
800                TraceField::RefundAddress
801            }
802            crate::hypersync_net_types_capnp::TraceField::RewardType => TraceField::RewardType,
803            crate::hypersync_net_types_capnp::TraceField::Sighash => TraceField::Sighash,
804            crate::hypersync_net_types_capnp::TraceField::Subtraces => TraceField::Subtraces,
805            crate::hypersync_net_types_capnp::TraceField::TraceAddress => TraceField::TraceAddress,
806            crate::hypersync_net_types_capnp::TraceField::Value => TraceField::Value,
807        }
808    }
809}
810
811#[cfg(test)]
812mod tests {
813    use hypersync_format::Hex;
814
815    use super::*;
816    use crate::{query::tests::test_query_serde, Query};
817
818    #[test]
819    fn test_all_fields_in_schema() {
820        let schema = hypersync_schema::trace();
821        let schema_fields = schema
822            .fields
823            .iter()
824            .map(|f| f.name().clone())
825            .collect::<std::collections::BTreeSet<_>>();
826        let all_fields = TraceField::all()
827            .into_iter()
828            .map(|f| f.as_ref().to_string())
829            .collect::<std::collections::BTreeSet<_>>();
830        assert_eq!(schema_fields, all_fields);
831    }
832
833    #[test]
834    fn test_serde_matches_strum() {
835        for field in TraceField::all() {
836            let serialized = serde_json::to_string(&field).unwrap();
837            let strum = serde_json::to_string(&field.as_ref()).unwrap();
838            assert_eq!(serialized, strum, "strum value should be the same as serde");
839        }
840    }
841
842    #[test]
843    fn test_trace_filter_serde_with_defaults() {
844        let trace_filter = TraceSelection::default();
845        let query = Query::new()
846            .where_traces(trace_filter)
847            .select_trace_fields(TraceField::all());
848
849        test_query_serde(query, "trace selection with defaults");
850    }
851
852    #[test]
853    fn test_trace_filter_serde_with_full_values() {
854        let trace_filter = TraceFilter {
855            from: vec![Address::decode_hex("0xdadB0d80178819F2319190D340ce9A924f783711").unwrap()],
856            from_filter: Some(FilterWrapper::new(16, 1)),
857            to: vec![Address::decode_hex("0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6").unwrap()],
858            to_filter: Some(FilterWrapper::new(16, 1)),
859            address: vec![
860                Address::decode_hex("0x1234567890123456789012345678901234567890").unwrap(),
861            ],
862            address_filter: Some(FilterWrapper::new(16, 1)),
863            call_type: vec!["call".to_string(), "create".to_string()],
864            reward_type: vec!["block".to_string(), "uncle".to_string()],
865            type_: vec!["call".to_string(), "create".to_string()],
866            sighash: vec![Sighash::from([0x12, 0x34, 0x56, 0x78])],
867        };
868        let query = Query::new()
869            .where_traces(trace_filter)
870            .select_trace_fields(TraceField::all());
871
872        test_query_serde(query, "trace selection with full values");
873    }
874
875    #[test]
876    fn nullable_fields() {
877        use std::collections::HashMap;
878
879        let is_nullable_map: HashMap<_, _> = TraceField::all()
880            .iter()
881            .map(|f| (f.to_string(), f.is_nullable()))
882            .collect();
883        for field in hypersync_schema::trace().fields.iter() {
884            let should_be_nullable = is_nullable_map.get(field.name().as_str()).unwrap();
885            assert_eq!(
886                field.is_nullable(),
887                *should_be_nullable,
888                "field {} nullable mismatch",
889                field.name()
890            );
891        }
892    }
893}