1use bytemuck::{Pod, Zeroable};
8#[cfg(feature = "decimal")]
9use rust_decimal::Decimal;
10
11use crate::error::BasisPointsError;
12pub mod error;
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Zeroable, Pod)]
15#[cfg_attr(
16 feature = "anchor",
17 derive(
18 anchor_lang::InitSpace,
19 anchor_lang::prelude::AnchorSerialize,
20 anchor_lang::prelude::AnchorDeserialize
21 )
22)]
23#[repr(C)]
24pub struct BasisPoints(u16);
26
27impl BasisPoints {
28 pub const MAX: u16 = 10000;
30
31 pub fn new(bps: u16) -> Result<Self, BasisPointsError> {
35 if bps > Self::MAX {
36 return Err(BasisPointsError::InvalidBasisPoints);
37 }
38
39 Ok(Self(bps))
40 }
41}
42
43impl From<BasisPoints> for u16 {
44 fn from(bps: BasisPoints) -> Self {
45 bps.0
46 }
47}
48
49impl From<BasisPoints> for u32 {
50 fn from(bps: BasisPoints) -> Self {
51 u32::from(bps.0)
52 }
53}
54
55impl From<BasisPoints> for u64 {
56 fn from(bps: BasisPoints) -> Self {
57 u64::from(bps.0)
58 }
59}
60
61impl From<BasisPoints> for u128 {
62 fn from(bps: BasisPoints) -> Self {
63 u128::from(bps.0)
64 }
65}
66
67#[cfg(feature = "decimal")]
68impl From<BasisPoints> for Decimal {
69 fn from(bps: BasisPoints) -> Self {
70 Decimal::from(bps.0)
71 }
72}
73
74#[cfg(feature = "decimal")]
75impl TryFrom<Decimal> for BasisPoints {
76 type Error = BasisPointsError;
77
78 fn try_from(decimal: Decimal) -> Result<Self, Self::Error> {
79 let bps = decimal
80 .try_into()
81 .map_err(|_| BasisPointsError::ConversionFailed)?;
82 BasisPoints::new(bps)
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn test_new() {
92 assert_eq!(BasisPoints::new(10000), Ok(BasisPoints(10000)));
93 }
94
95 #[test]
96 fn test_new_error() {
97 assert_eq!(
98 BasisPoints::new(10001),
99 Err(BasisPointsError::InvalidBasisPoints)
100 );
101 }
102
103 #[cfg(feature = "decimal")]
104 #[test]
105 fn test_from_basis_points_to_decimal() {
106 let bps = BasisPoints::new(1234).unwrap();
107 let decimal: Decimal = bps.into();
108
109 assert_eq!(decimal, Decimal::new(1234, 0));
110 }
111
112 #[cfg(feature = "decimal")]
113 #[test]
114 fn test_try_from_decimal_to_basis_points() {
115 let decimal = Decimal::new(4321, 0);
116 let bps = BasisPoints::try_from(decimal);
117
118 assert_eq!(bps, Ok(BasisPoints(4321)));
119 }
120
121 #[cfg(feature = "decimal")]
122 #[test]
123 fn test_round_trip_basis_points_decimal_basis_points() {
124 let original = BasisPoints::new(9999).unwrap();
125 let decimal: Decimal = original.into();
126 let round_trip = BasisPoints::try_from(decimal);
127
128 assert_eq!(round_trip, Ok(original));
129 }
130
131 #[cfg(feature = "decimal")]
132 #[test]
133 fn test_try_from_decimal_fractional_truncates() {
134 let decimal = Decimal::new(12345, 1); let bps = BasisPoints::try_from(decimal);
136
137 assert_eq!(bps, Ok(BasisPoints(1234)));
138 }
139
140 #[cfg(feature = "decimal")]
141 #[test]
142 fn test_try_from_decimal_above_basis_points_max_error() {
143 let decimal = Decimal::new(10001, 0);
144 let bps = BasisPoints::try_from(decimal);
145
146 assert_eq!(bps, Err(BasisPointsError::InvalidBasisPoints));
147 }
148
149 #[cfg(feature = "decimal")]
150 #[test]
151 fn test_try_from_decimal_u16_overflow_error() {
152 let decimal = Decimal::new(70000, 0);
153 let bps = BasisPoints::try_from(decimal);
154
155 assert_eq!(bps, Err(BasisPointsError::ConversionFailed));
156 }
157}