templar_proxy_oracle_kernel/proxy/aggregator/method/
priority.rs

1use alloc::vec::Vec;
2#[cfg(any(feature = "borsh", feature = "schemars"))]
3use alloc::{format, string::ToString};
4
5use crate::Price;
6
7use super::Aggregate;
8
9serialize! {
10    #[derive(Debug, Clone, PartialEq, Eq)]
11    pub struct Priority<S> {
12        pub sources: Vec<S>,
13    }
14}
15
16impl<S> Priority<S> {
17    pub fn new(sources: impl IntoIterator<Item = S>) -> Self {
18        Self {
19            sources: sources.into_iter().collect(),
20        }
21    }
22}
23
24impl<S> Aggregate<S> for Priority<S> {
25    fn aggregate<I>(&self, prices: I) -> Result<Price, super::Error>
26    where
27        I: IntoIterator<Item = Option<Price>>,
28        I::IntoIter: ExactSizeIterator<Item = Option<Price>>,
29    {
30        let prices = prices.into_iter();
31
32        if prices.len() != self.sources.len() {
33            return Err(super::Error::LengthMismatch {
34                expected: self.sources.len(),
35                actual: prices.len(),
36            });
37        }
38
39        prices
40            .flatten()
41            .next()
42            .ok_or(super::Error::TooFewValidSources {
43                expected: 1,
44                actual: 0,
45            })
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use alloc::vec;
52
53    use super::*;
54    use crate::proxy::aggregator::method::Error;
55
56    fn price(value: i64, conf: u64, publish_time_s: u64) -> Price {
57        Price {
58            price: value,
59            conf,
60            expo: -6,
61            publish_time_ns: templar_primitives::Nanoseconds::from_secs(publish_time_s),
62        }
63    }
64
65    fn priority(count: usize) -> Priority<&'static str> {
66        Priority {
67            sources: (0..count).map(|_| "source").collect(),
68        }
69    }
70
71    #[rstest::rstest]
72    #[case(Priority::new([]), vec![])]
73    #[case(Priority::new(["s1", "s2"]), vec![None, None])]
74    fn priority_returns_too_few_valid_sources_when_no_prices_survive(
75        #[case] aggregator: Priority<&'static str>,
76        #[case] prices: Vec<Option<Price>>,
77    ) {
78        let error = aggregator.aggregate(prices).unwrap_err();
79
80        assert!(matches!(
81            error,
82            Error::TooFewValidSources {
83                expected: 1,
84                actual: 0,
85            }
86        ));
87    }
88
89    #[test]
90    fn priority_returns_length_mismatch_when_prices_len_differs_from_sources() {
91        let error = priority(2)
92            .aggregate(vec![Some(price(1_000_000, 0, 0))])
93            .unwrap_err();
94
95        assert!(matches!(
96            error,
97            Error::LengthMismatch {
98                expected: 2,
99                actual: 1,
100            }
101        ));
102    }
103
104    #[test]
105    fn priority_single_price() {
106        let result = priority(1)
107            .aggregate(vec![Some(price(1_000_000, 0, 0))])
108            .unwrap();
109        assert_eq!(result.price, 1_000_000);
110    }
111
112    #[test]
113    fn priority_selects_first_valid_price() {
114        let prices = vec![
115            None,
116            Some(price(2_000_000, 0, 0)),
117            Some(price(3_000_000, 0, 0)),
118        ];
119        let result = priority(prices.len()).aggregate(prices).unwrap();
120        assert_eq!(result.price, 2_000_000);
121    }
122
123    #[test]
124    fn priority_preserves_original_price_with_confidence() {
125        let result = priority(2)
126            .aggregate(vec![Some(price(1_000, 100, 0)), Some(price(2_000, 0, 0))])
127            .unwrap();
128        assert_eq!(result.price, 1_000);
129        assert_eq!(result.conf, 100);
130    }
131
132    #[test]
133    fn priority_returns_first_valid_price_even_with_multiple_prices() {
134        let prices = vec![Some(price(1_000_000, 0, 0)), Some(price(2_000_000, 0, 0))];
135        let result = priority(prices.len()).aggregate(prices).unwrap();
136        assert_eq!(result.price, 1_000_000);
137    }
138}