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