Skip to main content

bullet_exchange_interface/decimals/
fixed_positive_decimal.rs

1use super::{PositiveDecimal, RoundingMode};
2
3pub const FIXED_DECIMALS: u32 = 12;
4
5#[derive(
6    Default,
7    Debug,
8    Clone,
9    Copy,
10    PartialEq,
11    Eq,
12    PartialOrd,
13    Ord,
14    serde::Serialize,
15    serde::Deserialize,
16    borsh::BorshSerialize,
17    borsh::BorshDeserialize,
18)]
19#[cfg_attr(feature = "schema", derive(sov_universal_wallet::UniversalWallet))]
20#[serde(transparent)]
21pub struct FixedPositiveDecimal(PositiveDecimal);
22
23impl std::fmt::Display for FixedPositiveDecimal {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        write!(f, "{}", self.0)
26    }
27}
28
29impl From<PositiveDecimal> for FixedPositiveDecimal {
30    fn from(value: PositiveDecimal) -> Self {
31        Self::new(value, RoundingMode::Down)
32    }
33}
34
35impl From<FixedPositiveDecimal> for PositiveDecimal {
36    fn from(value: FixedPositiveDecimal) -> PositiveDecimal {
37        value.0
38    }
39}
40
41impl FixedPositiveDecimal {
42    pub const ZERO: FixedPositiveDecimal = FixedPositiveDecimal(PositiveDecimal::ZERO);
43
44    #[inline]
45    pub fn new(value: PositiveDecimal, mode: RoundingMode) -> Self {
46        let rounded = match mode {
47            RoundingMode::Up => value.as_dec().round_dp_with_strategy(
48                FIXED_DECIMALS,
49                rust_decimal::RoundingStrategy::AwayFromZero,
50            ),
51            RoundingMode::Down => value
52                .as_dec()
53                .round_dp_with_strategy(FIXED_DECIMALS, rust_decimal::RoundingStrategy::ToZero),
54        };
55
56        // SAFETY: Rounding down a PositiveDecimal (non-negative) with RoundingMode::Down
57        // always produces a valid FixedPositiveDecimal. The only way FixedPositiveDecimal::new
58        // can fail is if PositiveDecimal::new returns None, which only happens for negative
59        // values. Since we're rounding down a non-negative value, the result is always non-negative.
60        #[allow(
61            clippy::expect_used,
62            reason = "Rounding down a PositiveDecimal is infallible - result is always non-negative"
63        )]
64        Self(
65            PositiveDecimal::new(rounded).expect(
66                "rounding down a PositiveDecimal always produces valid FixedPositiveDecimal",
67            ),
68        )
69    }
70
71    #[inline(always)]
72    pub fn is_zero(&self) -> bool {
73        self.0.is_zero()
74    }
75
76    #[inline(always)]
77    pub fn as_pos_dec(&self) -> PositiveDecimal {
78        self.0
79    }
80}