Skip to main content

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