xrpl/utils/
xrpl_conversion.rs1use crate::utils::exceptions::XRPRangeException;
4use alloc::format;
5use alloc::string::String;
6use alloc::string::ToString;
7use bigdecimal::BigDecimal;
8use regex::Regex;
9use rust_decimal::prelude::*;
10use rust_decimal::Decimal;
11
12use super::exceptions::XRPLUtilsResult;
13
14pub(crate) const _ONE_DROP: Decimal = Decimal::from_parts(1, 0, 0, false, 6);
16
17pub const ONE_DROP: &str = "0.000001";
19pub const MAX_XRP: u64 = u64::pow(10, 11);
21pub const MAX_DROPS: u64 = u64::pow(10, 17);
23pub const XRP_DROPS: u64 = 1000000;
25pub const MIN_IOU_EXPONENT: i32 = -96;
27pub const MAX_IOU_EXPONENT: i32 = 80;
29pub const MAX_IOU_PRECISION: u8 = 16;
31
32fn checked_rem(first: &BigDecimal, second: &BigDecimal) -> Option<BigDecimal> {
34 if second.is_zero() {
36 return None;
37 }
38
39 match checked_div(first, second) {
41 Some(div) => {
42 let int_part = div.with_scale(0); let rem = first - &(int_part * second);
47
48 Some(rem)
49 }
50 None => None, }
52}
53
54fn checked_div(first: &BigDecimal, second: &BigDecimal) -> Option<BigDecimal> {
56 if second.is_zero() {
58 return None;
59 }
60
61 Some(first / second)
63}
64
65fn checked_mul(first: &BigDecimal, second: &BigDecimal) -> Option<BigDecimal> {
67 let result = first * second;
69
70 Some(result)
73}
74
75fn _calculate_precision(value: &str) -> XRPLUtilsResult<usize> {
78 let decimal = BigDecimal::from_str(value)?.normalized();
79 let regex = Regex::new("[^1-9]").expect("_calculate_precision");
80
81 if checked_rem(&decimal, &BigDecimal::one()).is_some() {
82 let stripped = regex
83 .replace(&decimal.to_string(), "")
84 .replace(['.', '0'], "");
85 Ok(stripped.len())
86 } else {
87 let quantized = decimal.with_scale(2);
88 let stripped = regex
89 .replace(&quantized.to_string(), "")
90 .replace(['.', '0'], "");
91 Ok(stripped.len())
92 }
93}
94
95fn _verify_no_decimal(decimal: BigDecimal) -> XRPLUtilsResult<()> {
98 let (mantissa, scale) = decimal.as_bigint_and_exponent();
99 let decimal = BigDecimal::from_i64(scale).expect("_verify_no_decimal");
100
101 let value: String = if decimal == BigDecimal::zero() {
102 mantissa.to_string()
103 } else {
104 (&decimal * &decimal).to_string()
105 };
110
111 if value.contains('.') {
112 Err(XRPRangeException::InvalidValueContainsDecimal.into())
113 } else {
114 Ok(())
115 }
116}
117
118pub fn xrp_to_drops(xrp: &str) -> XRPLUtilsResult<String> {
144 let xrp_d = Decimal::from_str(xrp)?;
145
146 if xrp_d < _ONE_DROP && xrp_d != Decimal::ZERO {
147 Err(XRPRangeException::InvalidXRPAmountTooSmall {
148 min: ONE_DROP.to_string(),
149 found: xrp.to_string(),
150 }
151 .into())
152 } else if xrp_d.gt(&Decimal::new(MAX_XRP as i64, 0)) {
153 Err(XRPRangeException::InvalidXRPAmountTooLarge {
154 max: MAX_XRP,
155 found: xrp.into(),
156 }
157 .into())
158 } else {
159 Ok(format!("{}", (xrp_d / _ONE_DROP).trunc()))
160 }
161}
162
163pub fn drops_to_xrp(drops: &str) -> XRPLUtilsResult<String> {
188 let drops_d = Decimal::from_str(drops)?;
189 let xrp = drops_d * _ONE_DROP;
190
191 if xrp.gt(&Decimal::new(MAX_XRP as i64, 0)) {
192 Err(XRPRangeException::InvalidDropsAmountTooLarge {
193 max: MAX_XRP.to_string(),
194 found: drops.to_string(),
195 }
196 .into())
197 } else {
198 Ok(xrp.normalize().to_string())
199 }
200}
201
202pub fn verify_valid_xrp_value(xrp_value: &str) -> XRPLUtilsResult<()> {
224 let decimal = Decimal::from_str(xrp_value)?;
225 let max = Decimal::new(MAX_DROPS as i64, 0);
226
227 match decimal {
228 xrp if xrp.is_zero() => Ok(()),
229 xrp if xrp.ge(&_ONE_DROP) && xrp.le(&max) => Ok(()),
230 xrp if xrp.lt(&_ONE_DROP) => Err(XRPRangeException::InvalidXRPAmountTooSmall {
231 min: ONE_DROP.to_string(),
232 found: xrp.to_string(),
233 }
234 .into()),
235 xrp if xrp.gt(&max) => Err(XRPRangeException::InvalidDropsAmountTooLarge {
236 max: MAX_XRP.to_string(),
237 found: xrp.to_string(),
238 }
239 .into()),
240 _ => Err(XRPRangeException::InvalidXRPAmount.into()),
242 }
243}
244
245pub fn verify_valid_ic_value(ic_value: &str) -> XRPLUtilsResult<()> {
267 let decimal = BigDecimal::from_str(ic_value)?.normalized();
268 let scale = -(decimal.fractional_digit_count() as i32);
269 let prec = _calculate_precision(ic_value)?;
270
271 match decimal {
272 ic if ic.is_zero() => Ok(()),
273 _ if prec > MAX_IOU_PRECISION as usize || scale > MAX_IOU_EXPONENT => {
274 Err(XRPRangeException::InvalidICPrecisionTooLarge {
275 max: MAX_IOU_EXPONENT,
276 found: scale,
277 }
278 .into())
279 }
280 _ if prec > MAX_IOU_PRECISION as usize || scale < MIN_IOU_EXPONENT => {
281 Err(XRPRangeException::InvalidICPrecisionTooSmall {
282 min: MIN_IOU_EXPONENT,
283 found: scale,
284 }
285 .into())
286 }
287 _ => _verify_no_decimal(decimal),
288 }
289}
290
291#[cfg(test)]
292mod test {
293 use super::*;
294 use crate::alloc::string::ToString;
295 extern crate std;
296
297 #[test]
298 fn test_one_drop_decimal() {
299 assert_eq!(Ok(_ONE_DROP), Decimal::from_str("0.000001"));
300 }
301
302 #[test]
303 fn test_xrp_to_drops() {
304 assert_eq!(
305 Ok((100 * XRP_DROPS + 1).to_string()),
306 xrp_to_drops("100.000001")
307 );
308 }
309
310 #[test]
311 fn test_drops_to_xrp() {
312 assert_eq!(
313 drops_to_xrp("100000001"),
314 Ok(Decimal::new(100000001, 6).to_string())
315 );
316 }
317
318 #[test]
319 fn test_verify_valid_xrp_value() {
320 assert!(verify_valid_xrp_value(ONE_DROP).is_ok());
321 assert!(verify_valid_xrp_value(&MAX_DROPS.to_string()).is_ok());
322 assert!(verify_valid_xrp_value("0.0000001").is_err());
323 assert!(verify_valid_xrp_value("100000000000000001").is_err());
324 }
325
326 #[test]
327 fn test_verify_valid_ic_value() {
328 let valid = [
330 "0",
331 "0.0",
332 "1",
333 "1.1111",
334 "-1",
335 "-1.1",
336 "1111111111111111.0",
337 "-1111111111111111.0",
338 "0.00000000001",
339 "0.00000000001",
340 "-0.00000000001",
341 "0.001111111111111111",
342 "-0.001111111111111111",
343 ];
344
345 let invalid = ["-0.0011111111111111111"];
346
347 for case in valid {
348 assert!(verify_valid_ic_value(case).is_ok());
349 }
350
351 for case in invalid {
352 assert!(verify_valid_ic_value(case).is_err());
353 }
354 }
355
356 #[test]
357 fn accept_one_xrp() {
358 assert_eq!(xrp_to_drops("1"), Ok("1000000".to_string()));
359 }
360
361 #[test]
362 fn accept_zero_xrp() {
363 assert_eq!(xrp_to_drops("0"), Ok("0".to_string()));
364 }
365
366 #[test]
367 fn accept_min_xrp() {
368 assert_eq!(xrp_to_drops("0.000001"), Ok("1".to_string()));
369 }
370
371 #[test]
372 fn accept_max_xrp() {
373 assert_eq!(
374 xrp_to_drops(&MAX_XRP.to_string()),
375 Ok("100000000000000000".to_string())
376 );
377 }
378
379 #[test]
380 fn accept_too_small_xrp() {
381 assert!(xrp_to_drops("0.0000001").is_err());
382 }
383
384 #[test]
385 fn accept_too_big_xrp() {
386 let xrp = (MAX_XRP + 1).to_string();
387 assert!(xrp_to_drops(&xrp).is_err());
388 }
389
390 #[test]
391 fn accept_one_drop() {
392 assert_eq!(drops_to_xrp("1"), Ok(ONE_DROP.to_string()));
393 }
394
395 #[test]
396 fn accept_zero_drops() {
397 assert_eq!(drops_to_xrp("0"), Ok("0".to_string()));
398 }
399
400 #[test]
401 fn accept_1mil_drops() {
402 assert_eq!(drops_to_xrp("1000000"), Ok(Decimal::new(1, 0).to_string()));
403 }
404
405 #[test]
406 fn accept_max_drops() {
407 assert_eq!(
408 drops_to_xrp(&MAX_DROPS.to_string()),
409 Ok(Decimal::new(MAX_XRP as i64, 0).to_string())
410 );
411 }
412
413 #[test]
414 fn accept_too_big_drops() {
415 assert!(xrp_to_drops(&(MAX_XRP + 1).to_string()).is_err());
416 }
417}