templar_common/oracle/redstone/
feed_data.rs

1use near_sdk::json_types::{I64, U64};
2use primitive_types::U256;
3use templar_primitives::{strnum::SU256, time::Nanoseconds};
4
5use crate::oracle::pyth::{self, PythTimestamp};
6
7#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
8#[near_sdk::near(serializers = [json, borsh])]
9pub struct FeedData {
10    pub price: SU256,
11    /// Package timestamp in nanoseconds since Unix epoch.
12    pub package_timestamp: Nanoseconds,
13    /// Write timestamp in nanoseconds since Unix epoch.
14    pub write_timestamp: Nanoseconds,
15}
16
17impl FeedData {
18    /// Converts this [`FeedData`] to a [`pyth::Price`], with the confidence
19    /// set to zero, because RedStone does not provide confidence intervals.
20    pub fn to_pyth_price(&self) -> Option<pyth::Price> {
21        let (price, exponent) = approximate_u256(self.price.into());
22        Some(pyth::Price {
23            price: I64(price),
24            conf: U64(0),
25            expo: exponent.checked_sub(super::DECIMALS)?,
26            // Publish time is in seconds, but the RedStone data uses nanoseconds.
27            publish_time: PythTimestamp::try_from_time(self.package_timestamp)?,
28        })
29    }
30}
31
32/// Use instead of `U256::exp10` to avoid stack overflow for large exponents,
33/// since `U256::exp10` uses linear-time recursion.
34fn u256_exp10(mut exponent: u32) -> U256 {
35    if exponent == 0 {
36        return U256::one();
37    }
38    let mut y = U256::one();
39    let mut x = U256::from(10);
40
41    while exponent > 1 {
42        if exponent % 2 == 1 {
43            y *= x;
44        }
45        x *= x;
46        exponent >>= 1;
47    }
48
49    x * y
50}
51
52/// Converts a [`U256`] to an `i64` mantissa and an `i32` exponent.
53///
54/// Rounds down (floor).
55#[allow(
56    clippy::cast_possible_wrap,
57    clippy::cast_possible_truncation,
58    reason = "guaranteed safe"
59)]
60fn approximate_u256(input: U256) -> (i64, i32) {
61    const I64_MAX: U256 = U256([0x7FFF_FFFF_FFFF_FFFF, 0, 0, 0]);
62
63    let mut n = input;
64    let mut exponent = 0;
65
66    if let Some(b) = n.bits().checked_sub(64) {
67        // 103_873_643 / 345_060_773 ~= log(2)/log(10)
68        let e = b * 103_873_643 / 345_060_773;
69        let modulus = u256_exp10(e as u32);
70        n /= modulus;
71        exponent += e;
72    }
73
74    while n > I64_MAX {
75        n /= 10;
76        exponent += 1;
77    }
78
79    (n.low_u64() as i64, exponent as i32)
80}
81
82#[allow(clippy::cast_sign_loss)]
83#[cfg(test)]
84mod tests {
85    use near_sdk::serde_json;
86
87    use super::*;
88
89    #[rstest::rstest]
90    #[case::zero(U256::zero())]
91    #[case::one(U256::one())]
92    #[case::max(U256::MAX)]
93    #[case::normal_fits_i64(U256::from(777_777_777_777_777_777_i64))]
94    #[case::large_power_of_2(U256::from(2).pow(255.into()))]
95    #[case::large_other(U256::from(123_945).pow(12.into()))]
96    fn approximation(#[case] x: U256) {
97        let (n, e) = approximate_u256(x);
98        eprintln!("{n}*10^{e} ~= {x}");
99        assert_eq!(U256::from(n), x / U256::exp10(e as usize));
100    }
101
102    #[rstest::rstest]
103    fn approximation_exp10() {
104        for i in 0..=77_u32 {
105            let v = U256::exp10(i as usize);
106            let (n, e) = approximate_u256(v);
107            eprintln!("{i}:\t{n} * 10^{e}");
108            assert_eq!(n.ilog10() + e as u32, i);
109        }
110    }
111
112    #[test]
113    fn json() {
114        let fd = FeedData {
115            price: U256::from(3333).into(),
116            package_timestamp: Nanoseconds::from_ms(5555),
117            write_timestamp: Nanoseconds::from_ms(6666),
118        };
119
120        let serialized = serde_json::to_string(&fd).unwrap();
121
122        eprintln!("{serialized}");
123
124        assert_eq!(
125            serialized,
126            r#"{"price":"3333","package_timestamp":"5555000000","write_timestamp":"6666000000"}"#,
127        );
128
129        let deserialized: FeedData = serde_json::from_str(&serialized).unwrap();
130
131        assert_eq!(fd, deserialized);
132    }
133}