bullet_exchange_interface/decimals/
positive_decimal.rs1use std::io::Read;
2use std::ops::{Add, Div, Mul, Neg, Sub};
3use std::str::FromStr;
4
5use borsh::{BorshDeserialize, BorshSerialize};
6use rust_decimal::{Decimal, MathematicalOps};
7use serde::{Deserialize, Serialize};
8
9#[cfg(feature = "schema")]
10use super::SurrogateDecimal;
11use super::{FixedPositiveDecimal, RoundingMode, TryDecimalOps};
12use crate::error::{ArithmeticError, ArithmeticOperation, ConfigError};
13
14#[derive(
15 BorshSerialize,
16 Clone,
17 Copy,
18 Debug,
19 Default,
20 Deserialize,
21 Eq,
22 Hash,
23 Ord,
24 PartialEq,
25 PartialOrd,
26 Serialize,
27)]
28#[cfg_attr(feature = "schema", derive(sov_universal_wallet::UniversalWallet))]
29#[serde(into = "Decimal", try_from = "Decimal")]
30pub struct PositiveDecimal(
31 #[cfg_attr(feature = "schema", sov_wallet(as_ty = "SurrogateDecimal"))] Decimal,
32);
33
34impl std::fmt::Display for PositiveDecimal {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 write!(f, "{}", self.0)
37 }
38}
39
40impl FromStr for PositiveDecimal {
41 type Err = ConfigError;
42
43 fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
44 let decimal = Decimal::try_from_str(s)?;
45 Self::try_from(decimal)
46 }
47}
48
49impl From<u8> for PositiveDecimal {
50 fn from(value: u8) -> Self {
51 Self(Decimal::from(value))
52 }
53}
54
55impl From<u16> for PositiveDecimal {
56 fn from(value: u16) -> Self {
57 Self(Decimal::from(value))
58 }
59}
60
61impl From<u32> for PositiveDecimal {
62 fn from(value: u32) -> Self {
63 Self(Decimal::from(value))
64 }
65}
66
67impl From<u64> for PositiveDecimal {
68 fn from(value: u64) -> Self {
69 Self(Decimal::from(value))
70 }
71}
72
73impl TryFrom<Decimal> for PositiveDecimal {
74 type Error = ConfigError;
75
76 fn try_from(value: Decimal) -> Result<Self, ConfigError> {
77 Self::new(value).ok_or_else(|| ConfigError::FailedToParseInput {
78 input: value.to_string(),
79 reason: format!(
80 "Provided decimal value for {value} cannot be converted to the underlying type (PositiveDecimal).",
81 ),
82 })
83 }
84}
85
86impl From<PositiveDecimal> for Decimal {
87 #[inline]
88 fn from(value: PositiveDecimal) -> Self {
89 value.0
90 }
91}
92
93impl BorshDeserialize for PositiveDecimal {
94 fn deserialize_reader<R: Read>(reader: &mut R) -> std::io::Result<Self> {
95 let decimal = Decimal::deserialize_reader(reader)?;
96 Self::new(decimal).ok_or_else(|| {
97 std::io::Error::new(
98 std::io::ErrorKind::InvalidData,
99 format!("PositiveDecimal cannot be negative: {decimal}"),
100 )
101 })
102 }
103}
104
105impl schemars::JsonSchema for PositiveDecimal {
106 fn schema_name() -> String {
107 "PositiveDecimal".to_string()
108 }
109
110 fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
111 Decimal::json_schema(generator)
112 }
113}
114
115impl TryDecimalOps for PositiveDecimal {
116 #[inline]
117 fn try_from_str(value: &str) -> Result<Self, ConfigError> {
118 Self::from_str(value)
119 }
120
121 #[inline]
122 fn try_add(self, v: impl Into<Decimal>) -> Result<Self, ArithmeticError> {
123 let v = v.into();
124
125 self.as_dec()
126 .checked_add(v)
127 .and_then(Self::new)
128 .ok_or_else(|| ArithmeticError::DecimalFailed {
129 operation: ArithmeticOperation::Addition,
130 left: self.as_dec(),
131 right: v,
132 })
133 }
134
135 #[inline]
136 fn try_sub(self, v: impl Into<Decimal>) -> Result<Self, ArithmeticError> {
137 let v = v.into();
138
139 #[cfg(test)]
141 if self.as_dec().lt(&v) {
142 panic!(
143 "Unintended subtraction for PositiveDecimal: {} - {}",
144 self, v
145 );
146 }
147
148 self.as_dec()
149 .checked_sub(v)
150 .and_then(Self::new)
151 .ok_or_else(|| ArithmeticError::DecimalFailed {
152 operation: ArithmeticOperation::Subtraction,
153 left: self.as_dec(),
154 right: v,
155 })
156 }
157
158 #[inline]
159 fn try_mul(self, v: impl Into<Decimal>) -> Result<Self, ArithmeticError> {
160 let v = v.into();
161 if v.is_sign_negative() {
162 return Err(ArithmeticError::DecimalFailed {
163 operation: ArithmeticOperation::Multiplication,
164 left: self.as_dec(),
165 right: v,
166 });
167 }
168 self.as_dec()
169 .checked_mul(v)
170 .and_then(Self::new)
171 .ok_or_else(|| ArithmeticError::DecimalFailed {
172 operation: ArithmeticOperation::Multiplication,
173 left: self.as_dec(),
174 right: v,
175 })
176 }
177
178 #[inline]
179 fn try_div(self, v: impl Into<Decimal>) -> Result<Self, ArithmeticError> {
180 let v = v.into();
181 if v.is_sign_negative() {
182 return Err(ArithmeticError::DecimalFailed {
183 operation: ArithmeticOperation::Division,
184 left: self.as_dec(),
185 right: v,
186 });
187 }
188 self.as_dec()
189 .checked_div(v)
190 .and_then(Self::new)
191 .ok_or_else(|| ArithmeticError::DecimalFailed {
192 operation: ArithmeticOperation::Division,
193 left: self.as_dec(),
194 right: v,
195 })
196 }
197
198 #[inline]
199 fn try_exp(self) -> Result<Self, ArithmeticError> {
200 self.as_dec()
201 .checked_exp()
202 .and_then(Self::new)
203 .ok_or_else(|| ArithmeticError::DecimalFailed {
204 operation: ArithmeticOperation::Exponentiation,
205 left: self.as_dec(),
206 right: Decimal::ZERO,
207 })
208 }
209}
210
211impl PositiveDecimal {
212 pub const ZERO: PositiveDecimal = PositiveDecimal(Decimal::ZERO);
214
215 pub const ONE: PositiveDecimal = PositiveDecimal(Decimal::ONE);
216
217 pub const TWO: PositiveDecimal = PositiveDecimal(Decimal::TWO);
218
219 pub const TEN: PositiveDecimal = PositiveDecimal(Decimal::TEN);
220
221 pub const ONE_HUNDRED: PositiveDecimal = PositiveDecimal(Decimal::ONE_HUNDRED);
222
223 pub const MAX: PositiveDecimal = PositiveDecimal(Decimal::MAX);
224
225 #[inline]
226 pub fn new(value: Decimal) -> Option<Self> {
227 if value.is_sign_positive() {
228 Some(PositiveDecimal(value))
229 } else {
230 None
231 }
232 }
233
234 #[inline]
235 pub fn to_fixed(&self, rounding_mode: RoundingMode) -> FixedPositiveDecimal {
236 FixedPositiveDecimal::new(*self, rounding_mode)
237 }
238
239 #[inline]
240 pub fn as_dec(&self) -> Decimal {
241 self.0
242 }
243
244 #[inline]
245 pub fn is_zero(&self) -> bool {
246 self.0.is_zero()
247 }
248
249 #[inline]
250 pub fn fract(&self) -> Decimal {
251 self.0.fract()
252 }
253
254 #[inline]
255 pub fn try_pow_i64(&self, v: i64) -> Result<PositiveDecimal, ArithmeticError> {
256 self.as_dec()
257 .checked_powi(v)
258 .and_then(Self::new)
259 .ok_or_else(|| ArithmeticError::DecimalFailed {
260 operation: ArithmeticOperation::Exponentiation,
261 left: self.as_dec(),
262 right: Decimal::from(v),
263 })
264 }
265
266 #[inline]
267 pub fn try_with_precision(&self, precision: i64) -> Result<PositiveDecimal, ArithmeticError> {
268 let scaling_factor = Self::TEN.try_pow_i64(precision)?;
269 self.try_mul(scaling_factor)
270 }
271
272 #[inline]
275 pub fn saturating_sub(self, v: impl Into<Decimal>) -> Self {
276 let v = v.into();
277 if self.as_dec().lt(&v) {
278 Self::ZERO
279 } else {
280 Self(self.as_dec().sub(v))
285 }
286 }
287}
288
289impl Neg for PositiveDecimal {
290 type Output = Decimal;
291
292 fn neg(self) -> Decimal {
293 self.as_dec().neg()
294 }
295}
296
297impl Add for PositiveDecimal {
299 type Output = Self;
300
301 fn add(self, other: Self) -> Self::Output {
302 Self(self.as_dec().add(other.as_dec()))
303 }
304}
305
306impl Sub for PositiveDecimal {
308 type Output = Decimal;
309
310 fn sub(self, other: Self) -> Self::Output {
311 self.as_dec().sub(other.as_dec())
312 }
313}
314
315impl Mul for PositiveDecimal {
316 type Output = Self;
317
318 fn mul(self, other: Self) -> Self::Output {
319 Self(self.as_dec().mul(other.as_dec()))
320 }
321}
322
323impl Div for PositiveDecimal {
324 type Output = Self;
325
326 fn div(self, other: Self) -> Self::Output {
327 Self(self.as_dec().div(other.as_dec()))
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use borsh::to_vec;
334
335 use super::*;
336
337 #[test]
338 fn serde_rejects_negative_decimal() {
339 let json = r#""-123.45""#;
340 let result = serde_json::from_str::<PositiveDecimal>(json);
341 assert!(result.is_err(), "Should reject negative decimal via serde");
342 }
343
344 #[test]
345 fn serde_accepts_positive_decimal() {
346 let json = r#""123.45""#;
347 let result = serde_json::from_str::<PositiveDecimal>(json);
348 assert!(result.is_ok(), "Should accept positive decimal via serde");
349 assert_eq!(result.unwrap().to_string(), "123.45");
350 }
351
352 #[test]
353 fn serde_accepts_zero() {
354 let json = r#""0""#;
355 let result = serde_json::from_str::<PositiveDecimal>(json);
356 assert!(result.is_ok(), "Should accept zero via serde");
357 }
358
359 #[test]
360 fn borsh_rejects_negative_decimal() {
361 let negative = Decimal::new(-12345, 2); let bytes = to_vec(&negative).expect("serialize negative decimal");
364
365 let result = PositiveDecimal::try_from_slice(&bytes);
366 assert!(result.is_err(), "Should reject negative decimal via borsh");
367 }
368
369 #[test]
370 fn borsh_accepts_positive_decimal() {
371 let positive = Decimal::new(12345, 2); let bytes = to_vec(&positive).expect("serialize positive decimal");
373
374 let result = PositiveDecimal::try_from_slice(&bytes);
375 assert!(result.is_ok(), "Should accept positive decimal via borsh");
376 assert_eq!(result.unwrap().to_string(), "123.45");
377 }
378
379 #[test]
380 fn borsh_roundtrip_preserves_value() {
381 let original = PositiveDecimal::new(Decimal::new(12345, 2)).unwrap();
382 let bytes = to_vec(&original).expect("serialize");
383 let restored = PositiveDecimal::try_from_slice(&bytes).expect("deserialize");
384 assert_eq!(original, restored);
385 }
386}