templar_common/oracle/redstone/
feed_data.rs1use 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 pub package_timestamp: Nanoseconds,
13 pub write_timestamp: Nanoseconds,
15}
16
17impl FeedData {
18 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: PythTimestamp::try_from_time(self.package_timestamp)?,
28 })
29 }
30}
31
32fn 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#[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 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}