Skip to main content

acta_maker_sdk/types/
decimal.rs

1//! Exact `rust_decimal` views of 1e9-scaled values and atom amounts.
2
3use rust_decimal::Decimal;
4
5use super::PRICE_SCALE;
6use super::ids::{Decimals, Price, Quantity, Strike};
7
8const PRICE_DECIMALS: u32 = 9;
9
10fn scaled(value: u64) -> Decimal {
11    Decimal::from_i128_with_scale(i128::from(value), PRICE_DECIMALS)
12}
13
14/// `value × multiplier`, rounded down; `None` for a negative or out-of-range result.
15fn floor_atoms(value: Decimal, multiplier: Decimal) -> Option<u64> {
16    let atoms = value.checked_mul(multiplier)?.floor();
17    u64::try_from(atoms).ok()
18}
19
20impl Strike {
21    #[must_use]
22    pub fn to_decimal(self) -> Decimal {
23        scaled(self.value())
24    }
25}
26
27impl Price {
28    #[must_use]
29    pub fn to_decimal(self) -> Decimal {
30        scaled(self.value())
31    }
32
33    /// Quote tokens per underlying token, rounded down to the 1e-9 grid.
34    #[must_use]
35    pub fn from_decimal_floor(value: Decimal) -> Option<Self> {
36        floor_atoms(value, Decimal::from(PRICE_SCALE)).map(Self::new)
37    }
38}
39
40impl Quantity {
41    /// `None` when `decimals` exceeds 28, the most `Decimal` represents.
42    #[must_use]
43    pub fn to_decimal(self, decimals: Decimals) -> Option<Decimal> {
44        let scale = u32::from(decimals.value());
45        (scale <= Decimal::MAX_SCALE)
46            .then(|| Decimal::from_i128_with_scale(i128::from(self.value()), scale))
47    }
48
49    /// Whole underlying tokens to atoms, rounded down.
50    #[must_use]
51    pub fn from_decimal_floor(units: Decimal, decimals: Decimals) -> Option<Self> {
52        let power = 10i128.checked_pow(u32::from(decimals.value()))?;
53        let multiplier = Decimal::try_from_i128_with_scale(power, 0).ok()?;
54        floor_atoms(units, multiplier).map(Self::new)
55    }
56}
57
58#[cfg(test)]
59#[path = "decimal_tests.rs"]
60mod tests;