substrate_stellar_sdk/
amount.rs1use crate::StellarSdkError;
2use core::convert::{AsRef, From, TryFrom, TryInto};
3use sp_std::str;
4
5pub const STROOPS_PER_LUMEN: i64 = 10_000_000;
6pub struct LumenAmount(pub f64);
7pub struct StroopAmount(pub i64);
8
9pub trait IntoAmount {
10 fn into_stroop_amount(self, allow_zero: bool) -> Result<i64, StellarSdkError>;
11}
12
13impl IntoAmount for StroopAmount {
14 fn into_stroop_amount(self, allow_zero: bool) -> Result<i64, StellarSdkError> {
15 if allow_zero {
16 match self.0 < 0 {
17 true => Err(StellarSdkError::AmountNegative),
18 false => Ok(self.0),
19 }
20 } else {
21 match self.0 <= 0 {
22 true => Err(StellarSdkError::AmountNonPositive),
23 false => Ok(self.0),
24 }
25 }
26 }
27}
28
29impl IntoAmount for LumenAmount {
30 fn into_stroop_amount(self, allow_zero: bool) -> Result<i64, StellarSdkError> {
31 let stroop_amount: StroopAmount = self.try_into()?;
32 stroop_amount.into_stroop_amount(allow_zero)
33 }
34}
35
36impl TryFrom<LumenAmount> for StroopAmount {
37 type Error = StellarSdkError;
38
39 fn try_from(value: LumenAmount) -> Result<Self, Self::Error> {
40 let float_stroops = value.0 * STROOPS_PER_LUMEN as f64;
41 if float_stroops > i64::MAX as f64 {
42 return Err(StellarSdkError::AmountOverflow)
43 }
44
45 Ok(StroopAmount(float_stroops as i64))
46 }
47}
48
49impl From<StroopAmount> for LumenAmount {
50 fn from(value: StroopAmount) -> Self {
51 LumenAmount(value.0 as f64 / STROOPS_PER_LUMEN as f64)
52 }
53}
54
55impl<T: AsRef<[u8]>> IntoAmount for T {
56 fn into_stroop_amount(self, allow_zero: bool) -> Result<i64, StellarSdkError> {
57 let string = self.as_ref();
58 let seperator_position = string.iter().position(|char| *char == b'.');
59
60 let (integer_part, decimals) = match seperator_position {
61 Some(seperator_position) => {
62 let decimals_length = string.len() - seperator_position - 1;
63 if decimals_length > 7 {
64 return Err(StellarSdkError::InvalidAmountString)
65 }
66 let mut decimals = [b'0'; 7];
67 decimals[..decimals_length].copy_from_slice(&string[seperator_position + 1..]);
68
69 (&string[..seperator_position], parse_integer(&decimals)?)
70 },
71 None => (&string[..], 0),
72 };
73
74 let integer_part = parse_integer(integer_part)?;
75
76 let result = match integer_part.checked_mul(STROOPS_PER_LUMEN) {
77 Some(result) => result,
78 None => return Err(StellarSdkError::AmountOverflow),
79 };
80
81 let result = match result.checked_add(decimals) {
82 Some(result) => result,
83 None => return Err(StellarSdkError::AmountOverflow),
84 };
85
86 if result == 0 && !allow_zero {
87 return Err(StellarSdkError::AmountNonPositive)
88 }
89
90 Ok(result)
91 }
92}
93
94fn parse_integer(slice: &[u8]) -> Result<i64, StellarSdkError> {
95 if !slice.iter().all(|char| (*char as char).is_ascii_digit()) {
96 return Err(StellarSdkError::InvalidAmountString)
97 }
98 let slice = str::from_utf8(slice).unwrap();
99 slice.parse().map_err(|_| StellarSdkError::InvalidAmountString)
100}
101
102#[cfg(test)]
103mod test {
104 use super::*;
105
106 #[test]
107 fn parse_lumen_string() {
108 assert_eq!("23".into_stroop_amount(true), Ok(230_000_000));
109 assert_eq!("922337203685".into_stroop_amount(true), Ok(9223372036850_000_000));
110 assert_eq!("922337203686".into_stroop_amount(true), Err(StellarSdkError::AmountOverflow));
111
112 assert_eq!("0.23".into_stroop_amount(true), Ok(2_300_000));
113 assert_eq!("0.232442".into_stroop_amount(true), Ok(2_324_420));
114 assert_eq!("14.2324426".into_stroop_amount(true), Ok(142_324_426));
115 assert_eq!("14.23244267".into_stroop_amount(true), Err(StellarSdkError::InvalidAmountString));
116
117 assert_eq!("420.".into_stroop_amount(true), Ok(4200_000_000));
118
119 assert_eq!("922337203685.4775807".into_stroop_amount(true), Ok(9223372036854775807));
121 assert_eq!("922337203685.4775807".into_stroop_amount(true), Ok(i64::MAX));
122
123 assert_eq!("922337203685.4775808".into_stroop_amount(true), Err(StellarSdkError::AmountOverflow));
125
126 assert_eq!(".".into_stroop_amount(true), Err(StellarSdkError::InvalidAmountString));
127 assert_eq!("".into_stroop_amount(true), Err(StellarSdkError::InvalidAmountString));
128
129 assert_eq!("243. 34".into_stroop_amount(true), Err(StellarSdkError::InvalidAmountString));
130 assert_eq!("243.+34".into_stroop_amount(true), Err(StellarSdkError::InvalidAmountString));
131 assert_eq!("+243.34".into_stroop_amount(true), Err(StellarSdkError::InvalidAmountString));
132 assert_eq!("243.34x".into_stroop_amount(true), Err(StellarSdkError::InvalidAmountString));
133 assert_eq!("24?.34x".into_stroop_amount(true), Err(StellarSdkError::InvalidAmountString));
134 }
135}