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
128
129
130
131
132
133
134
135
136
use near_sdk::{
    json_types::{Base64VecU8, U64},
    near, AccountId, Gas,
};

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) -> near_sdk::Promise {
        near_sdk::Promise::new(self.account_id.clone()).function_call(
            self.method_name.clone(),
            self.args.0.clone(),
            near_sdk::NearToken::from_near(0),
            Gas::from_gas(self.gas.0),
        )
    }

    #[cfg(not(target_arch = "wasm32"))]
    pub fn rpc_call(&self) -> near_primitives::views::QueryRequest {
        near_primitives::views::QueryRequest::CallFunction {
            account_id: self.account_id.clone(),
            method_name: self.method_name.clone(),
            args: self.args.0.clone().into(),
        }
    }
}

#[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(),
            },
        );
    }
}