templar_common/oracle/
price_transformer.rs

1use near_sdk::{
2    json_types::{Base64VecU8, U64},
3    near, AccountId, Gas,
4};
5
6use crate::number::Decimal;
7
8use super::pyth::{self, PriceIdentifier};
9
10#[derive(Clone, Debug, PartialEq, Eq)]
11#[near(serializers = [json, borsh])]
12pub enum Action {
13    NormalizeNativeLstPrice { decimals: u32 },
14}
15
16impl Action {
17    pub fn apply(&self, mut price: pyth::Price, input: Decimal) -> Option<pyth::Price> {
18        match self {
19            Self::NormalizeNativeLstPrice { decimals } => {
20                let scale_factor = input / 10u128.pow(*decimals);
21
22                let price_is_negative = if price.price.0.is_negative() { -1 } else { 1 };
23                let abs_price_u128 = i128::from(price.price.0).unsigned_abs();
24                price.price.0 = price_is_negative
25                    * i64::try_from((abs_price_u128 * scale_factor).to_u128_floor()?).ok()?;
26                price.conf.0 = u64::try_from((price.conf.0 * scale_factor).to_u128_ceil()?).ok()?;
27                Some(price)
28            }
29        }
30    }
31}
32
33#[derive(Clone, Debug, PartialEq, Eq)]
34#[near(serializers = [json, borsh])]
35pub struct Call {
36    pub account_id: AccountId,
37    pub method_name: String,
38    pub args: Base64VecU8,
39    pub gas: U64,
40}
41
42impl Call {
43    #[cfg(all(not(target_arch = "wasm32"), feature = "rpc"))]
44    #[allow(clippy::unwrap_used)]
45    pub fn new(
46        account_id: &near_sdk::AccountIdRef,
47        method_name: impl Into<String>,
48        args: impl near_sdk::serde::Serialize,
49        gas: Gas,
50    ) -> Self {
51        Self {
52            account_id: account_id.into(),
53            method_name: method_name.into(),
54            args: near_sdk::serde_json::to_vec(&args).unwrap().into(),
55            gas: gas.as_gas().into(),
56        }
57    }
58
59    #[cfg(all(not(target_arch = "wasm32"), feature = "rpc"))]
60    pub fn new_simple(account_id: &near_sdk::AccountIdRef, method_name: impl Into<String>) -> Self {
61        Self::new(
62            account_id,
63            method_name,
64            near_sdk::serde_json::Value::Null,
65            Gas::from_tgas(3),
66        )
67    }
68
69    pub fn promise(&self) -> near_sdk::Promise {
70        near_sdk::Promise::new(self.account_id.clone()).function_call(
71            self.method_name.clone(),
72            self.args.0.clone(),
73            near_sdk::NearToken::from_near(0),
74            Gas::from_gas(self.gas.0),
75        )
76    }
77
78    #[cfg(all(not(target_arch = "wasm32"), feature = "rpc"))]
79    pub fn rpc_call(&self) -> near_primitives::views::QueryRequest {
80        near_primitives::views::QueryRequest::CallFunction {
81            account_id: self.account_id.clone(),
82            method_name: self.method_name.clone(),
83            args: self.args.0.clone().into(),
84        }
85    }
86}
87
88#[derive(Clone, Debug, PartialEq, Eq)]
89#[near(serializers = [json, borsh])]
90pub struct PriceTransformer {
91    pub price_id: PriceIdentifier,
92    pub call: Call,
93    pub action: Action,
94}
95
96impl PriceTransformer {
97    pub fn lst(price_id: PriceIdentifier, decimals: u32, call: Call) -> Self {
98        Self {
99            price_id,
100            call,
101            action: Action::NormalizeNativeLstPrice { decimals },
102        }
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use crate::dec;
109
110    use super::*;
111
112    #[test]
113    fn price_transformation() {
114        let transformation = Action::NormalizeNativeLstPrice { decimals: 24 };
115        let price_before = pyth::Price {
116            price: 1234.into(),
117            conf: 4.into(),
118            expo: 5,
119            publish_time: 0.into(),
120        };
121
122        let price_after = transformation
123            .apply(price_before, dec!("1.2").mul_pow10(24).unwrap())
124            .unwrap();
125
126        assert_eq!(
127            price_after,
128            pyth::Price {
129                price: 1480.into(),
130                conf: 5.into(),
131                expo: 5,
132                publish_time: 0.into(),
133            },
134        );
135    }
136}