pyth_lazer_protocol/
price.rs

1#[cfg(test)]
2mod tests;
3
4use {
5    crate::ExponentFactor,
6    rust_decimal::{prelude::FromPrimitive, Decimal},
7    serde::{Deserialize, Serialize},
8    std::num::NonZeroI64,
9    thiserror::Error,
10    utoipa::ToSchema,
11};
12
13#[derive(Debug, Error)]
14pub enum PriceError {
15    #[error("decimal parse error: {0}")]
16    DecimalParse(#[from] rust_decimal::Error),
17    #[error("price value is more precise than available exponent")]
18    TooPrecise,
19    #[error("zero price is unsupported")]
20    ZeroPriceUnsupported,
21    #[error("overflow")]
22    Overflow,
23}
24
25#[derive(
26    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, ToSchema,
27)]
28#[repr(transparent)]
29#[schema(value_type = i64)]
30pub struct Price(NonZeroI64);
31
32impl Price {
33    pub fn from_integer(value: i64, exponent: i16) -> Result<Price, PriceError> {
34        let mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
35            ExponentFactor::Mul(coef) => value.checked_mul(coef).ok_or(PriceError::Overflow)?,
36            ExponentFactor::Div(coef) => value.checked_div(coef).ok_or(PriceError::Overflow)?,
37        };
38        let mantissa = NonZeroI64::new(mantissa).ok_or(PriceError::ZeroPriceUnsupported)?;
39        Ok(Self(mantissa))
40    }
41
42    pub fn parse_str(value: &str, exponent: i16) -> Result<Price, PriceError> {
43        let value: Decimal = value.parse()?;
44        let mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
45            ExponentFactor::Mul(coef) => value
46                .checked_mul(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
47                .ok_or(PriceError::Overflow)?,
48            ExponentFactor::Div(coef) => value
49                .checked_div(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
50                .ok_or(PriceError::Overflow)?,
51        };
52        if !mantissa.is_integer() {
53            return Err(PriceError::TooPrecise);
54        }
55        let mantissa: i64 = mantissa.try_into().map_err(|_| PriceError::Overflow)?;
56        let mantissa = NonZeroI64::new(mantissa).ok_or(PriceError::Overflow)?;
57        Ok(Self(mantissa))
58    }
59
60    pub const fn from_nonzero_mantissa(mantissa: NonZeroI64) -> Self {
61        Self(mantissa)
62    }
63
64    pub const fn from_mantissa(mantissa: i64) -> Result<Self, PriceError> {
65        if let Some(mantissa) = NonZeroI64::new(mantissa) {
66            Ok(Self(mantissa))
67        } else {
68            Err(PriceError::ZeroPriceUnsupported)
69        }
70    }
71
72    pub fn mantissa(self) -> NonZeroI64 {
73        self.0
74    }
75
76    pub fn mantissa_i64(self) -> i64 {
77        self.0.get()
78    }
79
80    pub fn to_f64(self, exponent: i16) -> Result<f64, PriceError> {
81        match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
82            // Mul/div is reversed for converting mantissa to value
83            ExponentFactor::Mul(coef) => Ok(self.0.get() as f64 / coef as f64),
84            ExponentFactor::Div(coef) => Ok(self.0.get() as f64 * coef as f64),
85        }
86    }
87
88    pub fn from_f64(value: f64, exponent: i16) -> Result<Self, PriceError> {
89        let value = Decimal::from_f64(value).ok_or(PriceError::Overflow)?;
90        let mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
91            ExponentFactor::Mul(coef) => value
92                .checked_mul(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
93                .ok_or(PriceError::Overflow)?,
94            ExponentFactor::Div(coef) => value
95                .checked_div(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
96                .ok_or(PriceError::Overflow)?,
97        };
98        let mantissa: i64 = mantissa.try_into().map_err(|_| PriceError::Overflow)?;
99        Ok(Self(
100            NonZeroI64::new(mantissa).ok_or(PriceError::ZeroPriceUnsupported)?,
101        ))
102    }
103
104    pub fn add_with_same_exponent(self, other: Price) -> Result<Self, PriceError> {
105        let mantissa = self
106            .0
107            .get()
108            .checked_add(other.0.get())
109            .ok_or(PriceError::Overflow)?;
110        Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
111    }
112
113    pub fn sub_with_same_exponent(self, other: Price) -> Result<Self, PriceError> {
114        let mantissa = self
115            .0
116            .get()
117            .checked_sub(other.0.get())
118            .ok_or(PriceError::Overflow)?;
119        Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
120    }
121
122    pub fn mul_integer(self, factor: i64) -> Result<Self, PriceError> {
123        let mantissa = self
124            .0
125            .get()
126            .checked_mul(factor)
127            .ok_or(PriceError::Overflow)?;
128        Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
129    }
130
131    pub fn div_integer(self, factor: i64) -> Result<Self, PriceError> {
132        let mantissa = self
133            .0
134            .get()
135            .checked_div(factor)
136            .ok_or(PriceError::Overflow)?;
137        Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
138    }
139
140    pub fn mul_decimal(self, mantissa: i64, exponent: i16) -> Result<Self, PriceError> {
141        let left_mantissa = i128::from(self.0.get());
142        let right_mantissa = i128::from(mantissa);
143
144        // multiplied_mantissas = left_mantissa * right_mantissa
145        let multiplied_mantissas = left_mantissa
146            .checked_mul(right_mantissa)
147            .ok_or(PriceError::Overflow)?;
148
149        // result_mantissa = left_mantissa * right_mantissa * 10^exponent
150        // Mul/div is reversed for multiplying 10^exponent
151        let result_mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
152            ExponentFactor::Mul(coef) => multiplied_mantissas
153                .checked_div(coef.into())
154                .ok_or(PriceError::Overflow)?,
155            ExponentFactor::Div(coef) => multiplied_mantissas
156                .checked_mul(coef.into())
157                .ok_or(PriceError::Overflow)?,
158        };
159        let result_mantissa: i64 = result_mantissa
160            .try_into()
161            .map_err(|_| PriceError::Overflow)?;
162        Self::from_mantissa(result_mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
163    }
164}