templar_proxy_oracle_kernel/
price.rs

1#[cfg(feature = "schemars")]
2use alloc::borrow::ToOwned;
3#[cfg(any(feature = "borsh", feature = "schemars"))]
4use alloc::string::ToString;
5use core::cmp::Ordering;
6
7use templar_primitives::time::Nanoseconds;
8
9serialize! {
10    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
11    pub struct Price {
12        pub price: i64,
13        /// Confidence interval around the price
14        pub conf: u64,
15        /// The exponent
16        pub expo: i32,
17        /// Unix timestamp of when this price was computed
18        pub publish_time_ns: Nanoseconds,
19    }
20}
21
22impl Price {
23    #[must_use]
24    pub fn has_strictly_positive_confidence_interval(&self) -> bool {
25        u64::try_from(self.price).is_ok_and(|price| price > self.conf)
26    }
27}
28
29// Compare signed decimal mantissas exactly without normalizing through a fixed-width scaled
30// integer. Once sign is handled, compare by decimal magnitude (`digits + exponent`) and only
31// rescale by the difference in mantissa digit counts, which is bounded for `i64` values.
32pub(crate) fn compare_scaled(
33    left_value: i64,
34    left_exponent: i32,
35    right_value: i64,
36    right_exponent: i32,
37) -> Ordering {
38    match (left_value.cmp(&0), right_value.cmp(&0)) {
39        (Ordering::Equal, right_sign) => return right_sign.reverse(),
40        (left_sign, Ordering::Equal) => return left_sign,
41        (left_sign, right_sign) if left_sign != right_sign => return left_sign,
42        _ => {}
43    }
44
45    let negative = left_value.is_negative();
46    let left_abs = u128::from(left_value.unsigned_abs());
47    let right_abs = u128::from(right_value.unsigned_abs());
48    let left_log10 = left_abs.ilog10();
49    let right_log10 = right_abs.ilog10();
50
51    let left_scale = i64::from(left_exponent) + i64::from(left_log10);
52    let right_scale = i64::from(right_exponent) + i64::from(right_log10);
53    let magnitude_order = left_scale.cmp(&right_scale).then_with(|| {
54        let max_digits = left_log10.max(right_log10);
55        let left_scaled = left_abs * 10u128.pow(max_digits - left_log10);
56        let right_scaled = right_abs * 10u128.pow(max_digits - right_log10);
57        left_scaled.cmp(&right_scaled)
58    });
59
60    if negative {
61        magnitude_order.reverse()
62    } else {
63        magnitude_order
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use templar_primitives::Nanoseconds;
70
71    use super::*;
72
73    fn price(price: i64, conf: u64) -> Price {
74        Price {
75            price,
76            conf,
77            expo: 0,
78            publish_time_ns: Nanoseconds::zero(),
79        }
80    }
81
82    #[rstest::rstest]
83    #[case(price(1, 0), true)]
84    #[case(price(2, 1), true)]
85    #[case(price(1, 1), false)]
86    #[case(price(1, 2), false)]
87    #[case(price(0, 0), false)]
88    #[case(price(-1, 0), false)]
89    fn validates_strictly_positive_confidence_interval(
90        #[case] price: Price,
91        #[case] expected: bool,
92    ) {
93        assert_eq!(price.has_strictly_positive_confidence_interval(), expected);
94    }
95}