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().checked_add(v).and_then(Self::new).ok_or_else(|| {
126 ArithmeticError::DecimalFailed {
127 operation: ArithmeticOperation::Addition,
128 left: self.as_dec(),
129 right: v,
130 }
131 })
132 }
133
134 #[inline]
135 fn try_sub(self, v: impl Into<Decimal>) -> Result<Self, ArithmeticError> {
136 let v = v.into();
137
138 #[cfg(test)]
140 if self.as_dec().lt(&v) {
141 panic!("Unintended subtraction for PositiveDecimal: {} - {}", self, v);
142 }
143
144 self.as_dec().checked_sub(v).and_then(Self::new).ok_or_else(|| {
145 ArithmeticError::DecimalFailed {
146 operation: ArithmeticOperation::Subtraction,
147 left: self.as_dec(),
148 right: v,
149 }
150 })
151 }
152
153 #[inline]
154 fn try_mul(self, v: impl Into<Decimal>) -> Result<Self, ArithmeticError> {
155 let v = v.into();
156 if v.is_sign_negative() {
157 return Err(ArithmeticError::DecimalFailed {
158 operation: ArithmeticOperation::Multiplication,
159 left: self.as_dec(),
160 right: v,
161 });
162 }
163 self.as_dec().checked_mul(v).and_then(Self::new).ok_or_else(|| {
164 ArithmeticError::DecimalFailed {
165 operation: ArithmeticOperation::Multiplication,
166 left: self.as_dec(),
167 right: v,
168 }
169 })
170 }
171
172 #[inline]
173 fn try_div(self, v: impl Into<Decimal>) -> Result<Self, ArithmeticError> {
174 let v = v.into();
175 if v.is_sign_negative() {
176 return Err(ArithmeticError::DecimalFailed {
177 operation: ArithmeticOperation::Division,
178 left: self.as_dec(),
179 right: v,
180 });
181 }
182 self.as_dec().checked_div(v).and_then(Self::new).ok_or_else(|| {
183 ArithmeticError::DecimalFailed {
184 operation: ArithmeticOperation::Division,
185 left: self.as_dec(),
186 right: v,
187 }
188 })
189 }
190
191 #[inline]
192 fn try_exp(self) -> Result<Self, ArithmeticError> {
193 self.as_dec().checked_exp().and_then(Self::new).ok_or_else(|| {
194 ArithmeticError::DecimalFailed {
195 operation: ArithmeticOperation::Exponentiation,
196 left: self.as_dec(),
197 right: Decimal::ZERO,
198 }
199 })
200 }
201}
202
203impl PositiveDecimal {
204 pub const ZERO: PositiveDecimal = PositiveDecimal(Decimal::ZERO);
206
207 pub const ONE: PositiveDecimal = PositiveDecimal(Decimal::ONE);
208
209 pub const TWO: PositiveDecimal = PositiveDecimal(Decimal::TWO);
210
211 pub const TEN: PositiveDecimal = PositiveDecimal(Decimal::TEN);
212
213 pub const ONE_HUNDRED: PositiveDecimal = PositiveDecimal(Decimal::ONE_HUNDRED);
214
215 pub const MAX: PositiveDecimal = PositiveDecimal(Decimal::MAX);
216
217 #[inline]
218 pub fn new(value: Decimal) -> Option<Self> {
219 if value.is_sign_positive() { Some(PositiveDecimal(value)) } else { None }
220 }
221
222 #[inline]
223 pub fn to_fixed(&self, rounding_mode: RoundingMode) -> FixedPositiveDecimal {
224 FixedPositiveDecimal::new(*self, rounding_mode)
225 }
226
227 #[inline]
228 pub fn as_dec(&self) -> Decimal {
229 self.0
230 }
231
232 #[inline]
233 pub fn is_zero(&self) -> bool {
234 self.0.is_zero()
235 }
236
237 #[inline]
238 pub fn fract(&self) -> Decimal {
239 self.0.fract()
240 }
241
242 #[inline]
243 pub fn try_pow_i64(&self, v: i64) -> Result<PositiveDecimal, ArithmeticError> {
244 self.as_dec().checked_powi(v).and_then(Self::new).ok_or_else(|| {
245 ArithmeticError::DecimalFailed {
246 operation: ArithmeticOperation::Exponentiation,
247 left: self.as_dec(),
248 right: Decimal::from(v),
249 }
250 })
251 }
252
253 #[inline]
254 pub fn try_with_precision(&self, precision: i64) -> Result<PositiveDecimal, ArithmeticError> {
255 let scaling_factor = Self::TEN.try_pow_i64(precision)?;
256 self.try_mul(scaling_factor)
257 }
258
259 #[inline]
263 pub fn saturating_sub(self, v: impl Into<Decimal>) -> Self {
264 let v = v.into();
265 if self.as_dec().lt(&v) {
266 Self::ZERO
267 } else {
268 Self(self.as_dec().sub(v))
273 }
274 }
275}
276
277impl Neg for PositiveDecimal {
278 type Output = Decimal;
279
280 fn neg(self) -> Decimal {
281 self.as_dec().neg()
282 }
283}
284
285impl Add for PositiveDecimal {
287 type Output = Self;
288
289 fn add(self, other: Self) -> Self::Output {
290 Self(self.as_dec().add(other.as_dec()))
291 }
292}
293
294impl Sub for PositiveDecimal {
296 type Output = Decimal;
297
298 fn sub(self, other: Self) -> Self::Output {
299 self.as_dec().sub(other.as_dec())
300 }
301}
302
303impl Mul for PositiveDecimal {
304 type Output = Self;
305
306 fn mul(self, other: Self) -> Self::Output {
307 Self(self.as_dec().mul(other.as_dec()))
308 }
309}
310
311impl Div for PositiveDecimal {
312 type Output = Self;
313
314 fn div(self, other: Self) -> Self::Output {
315 Self(self.as_dec().div(other.as_dec()))
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use borsh::to_vec;
322
323 use super::*;
324
325 #[test]
326 fn serde_rejects_negative_decimal() {
327 let json = r#""-123.45""#;
328 let result = serde_json::from_str::<PositiveDecimal>(json);
329 assert!(result.is_err(), "Should reject negative decimal via serde");
330 }
331
332 #[test]
333 fn serde_accepts_positive_decimal() {
334 let json = r#""123.45""#;
335 let result = serde_json::from_str::<PositiveDecimal>(json);
336 assert!(result.is_ok(), "Should accept positive decimal via serde");
337 assert_eq!(result.unwrap().to_string(), "123.45");
338 }
339
340 #[test]
341 fn serde_accepts_zero() {
342 let json = r#""0""#;
343 let result = serde_json::from_str::<PositiveDecimal>(json);
344 assert!(result.is_ok(), "Should accept zero via serde");
345 }
346
347 #[test]
348 fn borsh_rejects_negative_decimal() {
349 let negative = Decimal::new(-12345, 2); let bytes = to_vec(&negative).expect("serialize negative decimal");
352
353 let result = PositiveDecimal::try_from_slice(&bytes);
354 assert!(result.is_err(), "Should reject negative decimal via borsh");
355 }
356
357 #[test]
358 fn borsh_accepts_positive_decimal() {
359 let positive = Decimal::new(12345, 2); let bytes = to_vec(&positive).expect("serialize positive decimal");
361
362 let result = PositiveDecimal::try_from_slice(&bytes);
363 assert!(result.is_ok(), "Should accept positive decimal via borsh");
364 assert_eq!(result.unwrap().to_string(), "123.45");
365 }
366
367 #[test]
368 fn borsh_roundtrip_preserves_value() {
369 let original = PositiveDecimal::new(Decimal::new(12345, 2)).unwrap();
370 let bytes = to_vec(&original).expect("serialize");
371 let restored = PositiveDecimal::try_from_slice(&bytes).expect("deserialize");
372 assert_eq!(original, restored);
373 }
374}