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