defuse_fees/
lib.rs

1use core::{
2    fmt::{self, Display},
3    ops::{Add, Div, Mul, Not, Sub},
4};
5
6use defuse_num_utils::{CheckedAdd, CheckedMulDiv, CheckedSub};
7use thiserror::Error as ThisError;
8
9/// 1 pip == 1/100th of bip == 0.0001%
10#[cfg_attr(
11    feature = "borsh",
12    derive(::borsh::BorshSerialize),
13    cfg_attr(feature = "abi", derive(::borsh::BorshSchema))
14)]
15#[cfg_attr(
16    feature = "serde",
17    derive(::serde::Serialize, ::serde::Deserialize),
18    cfg_attr(feature = "abi", derive(::schemars::JsonSchema)),
19    serde(try_from = "u32")
20)]
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
22pub struct Pips(u32);
23
24impl Pips {
25    pub const ZERO: Self = Self(0);
26    pub const ONE_PIP: Self = Self(1);
27    pub const ONE_BIP: Self = Self(Self::ONE_PIP.as_pips() * 100);
28    pub const ONE_PERCENT: Self = Self(Self::ONE_BIP.as_pips() * 100);
29    pub const MAX: Self = Self(Self::ONE_PERCENT.as_pips() * 100);
30
31    #[inline]
32    pub const fn from_pips(pips: u32) -> Option<Self> {
33        if pips > Self::MAX.as_pips() {
34            return None;
35        }
36        Some(Self(pips))
37    }
38
39    #[inline]
40    pub const fn from_bips(bips: u32) -> Option<Self> {
41        Self::ONE_BIP.checked_mul(bips)
42    }
43
44    #[inline]
45    pub const fn from_percent(percent: u32) -> Option<Self> {
46        Self::ONE_PERCENT.checked_mul(percent)
47    }
48
49    #[inline]
50    pub const fn as_pips(self) -> u32 {
51        self.0
52    }
53
54    #[inline]
55    pub const fn as_bips(self) -> u32 {
56        self.as_pips() / Self::ONE_BIP.as_pips()
57    }
58
59    #[inline]
60    pub const fn as_percent(self) -> u32 {
61        self.as_pips() / Self::ONE_PERCENT.as_pips()
62    }
63
64    #[inline]
65    pub const fn is_zero(&self) -> bool {
66        self.0 == 0
67    }
68
69    #[inline]
70    pub fn as_f64(self) -> f64 {
71        f64::from(self.as_pips()) / f64::from(Self::MAX.as_pips())
72    }
73
74    pub const fn checked_add(self, rhs: Self) -> Option<Self> {
75        let Some(pips) = self.as_pips().checked_add(rhs.as_pips()) else {
76            return None;
77        };
78        Self::from_pips(pips)
79    }
80
81    pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
82        let Some(pips) = self.as_pips().checked_sub(rhs.as_pips()) else {
83            return None;
84        };
85        Self::from_pips(pips)
86    }
87
88    #[inline]
89    pub const fn checked_mul(self, rhs: u32) -> Option<Self> {
90        let Some(pips) = self.as_pips().checked_mul(rhs) else {
91            return None;
92        };
93        Self::from_pips(pips)
94    }
95
96    #[inline]
97    pub const fn checked_div(self, rhs: u32) -> Option<Self> {
98        let Some(pips) = self.as_pips().checked_div(rhs) else {
99            return None;
100        };
101        Some(Self(pips))
102    }
103
104    #[must_use]
105    #[inline]
106    pub const fn invert(self) -> Self {
107        Self(Self::MAX.as_pips() - self.as_pips())
108    }
109
110    #[inline]
111    pub fn fee(self, amount: u128) -> u128 {
112        amount
113            .checked_mul_div(self.as_pips().into(), Self::MAX.as_pips().into())
114            .unwrap_or_else(|| unreachable!())
115    }
116
117    #[inline]
118    pub fn fee_ceil(self, amount: u128) -> u128 {
119        amount
120            .checked_mul_div_ceil(self.as_pips().into(), Self::MAX.as_pips().into())
121            .unwrap_or_else(|| unreachable!())
122    }
123}
124
125impl CheckedAdd for Pips {
126    #[inline]
127    fn checked_add(self, rhs: Self) -> Option<Self> {
128        self.checked_add(rhs)
129    }
130}
131
132impl Add for Pips {
133    type Output = Self;
134
135    #[inline]
136    fn add(self, rhs: Self) -> Self::Output {
137        self.checked_add(rhs).unwrap()
138    }
139}
140
141impl CheckedSub for Pips {
142    #[inline]
143    fn checked_sub(self, rhs: Self) -> Option<Self> {
144        self.checked_sub(rhs)
145    }
146}
147
148impl Sub for Pips {
149    type Output = Self;
150
151    #[inline]
152    fn sub(self, rhs: Self) -> Self::Output {
153        self.checked_sub(rhs).unwrap()
154    }
155}
156
157impl Mul<u32> for Pips {
158    type Output = Self;
159
160    #[inline]
161    fn mul(self, rhs: u32) -> Self::Output {
162        self.checked_mul(rhs).unwrap()
163    }
164}
165
166impl Div<u32> for Pips {
167    type Output = Self;
168
169    #[inline]
170    fn div(self, rhs: u32) -> Self::Output {
171        self.checked_div(rhs).unwrap()
172    }
173}
174
175impl Not for Pips {
176    type Output = Self;
177
178    fn not(self) -> Self::Output {
179        self.invert()
180    }
181}
182
183impl Display for Pips {
184    #[inline]
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        write!(f, "{:.4}%", self.as_f64() * 100f64)
187    }
188}
189
190impl TryFrom<u32> for Pips {
191    type Error = PipsOutOfRange;
192
193    #[inline]
194    fn try_from(pips: u32) -> Result<Self, Self::Error> {
195        Self::from_pips(pips).ok_or(PipsOutOfRange)
196    }
197}
198
199#[derive(Debug, ThisError)]
200#[error("out of range: 0..={}", Pips::MAX.as_pips())]
201pub struct PipsOutOfRange;
202
203#[cfg(feature = "borsh")]
204impl ::borsh::BorshDeserialize for Pips {
205    fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
206        let pips = u32::deserialize_reader(reader)?;
207        Self::from_pips(pips).ok_or_else(|| {
208            std::io::Error::new(
209                std::io::ErrorKind::InvalidData,
210                format!("pips: {pips} is {PipsOutOfRange}"),
211            )
212        })
213    }
214}