templar_common/oracle/
price_transformer.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use near_sdk::{
    json_types::{Base64VecU8, U64},
    near, AccountId, Gas, NearToken, Promise,
};

use crate::number::Decimal;

use super::pyth::{self, PriceIdentifier};

#[derive(Clone, Debug, PartialEq, Eq)]
#[near(serializers = [json, borsh])]
pub enum Action {
    NormalizeNativeLstPrice { decimals: u32 },
}

impl Action {
    pub fn apply(&self, mut price: pyth::Price, input: Decimal) -> Option<pyth::Price> {
        match self {
            Self::NormalizeNativeLstPrice { decimals } => {
                let scale_factor = input / 10u128.pow(*decimals);

                let price_is_negative = if price.price.0.is_negative() { -1 } else { 1 };
                let abs_price_u128 = i128::from(price.price.0).unsigned_abs();
                price.price.0 = price_is_negative
                    * i64::try_from((abs_price_u128 * scale_factor).to_u128_floor()?).ok()?;
                price.conf.0 = u64::try_from((price.conf.0 * scale_factor).to_u128_ceil()?).ok()?;
                Some(price)
            }
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
#[near(serializers = [json, borsh])]
pub struct Call {
    pub account_id: AccountId,
    pub method_name: String,
    pub args: Base64VecU8,
    pub gas: U64,
}

impl Call {
    #[cfg(not(target_arch = "wasm32"))]
    #[allow(clippy::unwrap_used)]
    pub fn new(
        account_id: &near_sdk::AccountIdRef,
        method_name: impl Into<String>,
        args: impl near_sdk::serde::Serialize,
        gas: Gas,
    ) -> Self {
        Self {
            account_id: account_id.into(),
            method_name: method_name.into(),
            args: near_sdk::serde_json::to_vec(&args).unwrap().into(),
            gas: gas.as_gas().into(),
        }
    }

    #[cfg(not(target_arch = "wasm32"))]
    pub fn new_simple(account_id: &near_sdk::AccountIdRef, method_name: impl Into<String>) -> Self {
        Self::new(
            account_id,
            method_name,
            near_sdk::serde_json::Value::Null,
            Gas::from_tgas(3),
        )
    }

    pub fn promise(&self) -> Promise {
        Promise::new(self.account_id.clone()).function_call(
            self.method_name.clone(),
            self.args.0.clone(),
            NearToken::from_near(0),
            Gas::from_gas(self.gas.0),
        )
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
#[near(serializers = [json, borsh])]
pub struct PriceTransformer {
    pub price_id: PriceIdentifier,
    pub call: Call,
    pub action: Action,
}

impl PriceTransformer {
    pub fn lst(price_id: PriceIdentifier, decimals: u32, call: Call) -> Self {
        Self {
            price_id,
            call,
            action: Action::NormalizeNativeLstPrice { decimals },
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::dec;

    use super::*;

    #[test]
    fn price_transformation() {
        let transformation = Action::NormalizeNativeLstPrice { decimals: 24 };
        let price_before = pyth::Price {
            price: 1234.into(),
            conf: 4.into(),
            expo: 5,
            publish_time: 0.into(),
        };

        let price_after = transformation
            .apply(price_before, dec!("1.2").mul_pow10(24).unwrap())
            .unwrap();

        assert_eq!(
            price_after,
            pyth::Price {
                price: 1480.into(),
                conf: 5.into(),
                expo: 5,
                publish_time: 0.into(),
            },
        );
    }
}