templar_proxy_oracle_kernel/proxy/aggregator/method/
priority.rs1use 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}