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