1use crate::alloc::{format, string::ToString};
2use crate::{AccountId, CurrencyCode, Error};
3use core::fmt::Debug;
4
5#[derive(Debug, Eq, PartialEq, Clone, Copy)]
8pub enum Amount {
9 Issued(IssuedAmount),
10 Drops(DropsAmount),
11}
12
13impl Amount {
14 pub fn drops(drops: u64) -> Result<Self, Error> {
15 Ok(Self::Drops(DropsAmount::from_drops(drops)?))
16 }
17
18 pub fn issued(
19 value: IssuedValue,
20 currency: CurrencyCode,
21 issuer: AccountId,
22 ) -> Result<Self, Error> {
23 Ok(Self::Issued(IssuedAmount::from_issued_value(
24 value, currency, issuer,
25 )?))
26 }
27
28 pub fn is_drops(&self) -> bool {
29 matches!(self, Amount::Drops(_))
30 }
31
32 pub fn is_issued(&self) -> bool {
33 matches!(self, Amount::Issued(_))
34 }
35}
36
37#[derive(Debug, Eq, PartialEq, Clone, Copy)]
40pub struct DropsAmount(u64);
42
43impl DropsAmount {
44 pub fn from_drops(drops: u64) -> Result<Self, Error> {
45 if drops & (0b11 << 62) != 0 {
46 return Err(Error::OutOfRange(
47 "Drop amounts cannot use the two must significant bits".to_string(),
48 ));
49 }
50 Ok(Self(drops))
51 }
52
53 pub fn drops(&self) -> u64 {
55 self.0
56 }
57}
58
59#[derive(Debug, Eq, PartialEq, Clone, Copy)]
62pub struct IssuedAmount {
63 value: IssuedValue,
65 currency: CurrencyCode,
66 issuer: AccountId,
67}
68
69impl IssuedAmount {
70 pub fn from_issued_value(
71 value: IssuedValue,
72 currency: CurrencyCode,
73 issuer: AccountId,
74 ) -> Result<Self, Error> {
75 if currency.is_xrp() {
76 return Err(Error::InvalidData(
77 "Issued amount cannot have XRP currency code".to_string(),
78 ));
79 }
80 Ok(Self {
81 value,
82 currency,
83 issuer,
84 })
85 }
86
87 pub fn value(&self) -> IssuedValue {
89 self.value
90 }
91
92 pub fn currency(&self) -> CurrencyCode {
94 self.currency
95 }
96
97 pub fn issuer(&self) -> AccountId {
99 self.issuer
100 }
101}
102
103#[derive(Debug, Eq, PartialEq, Clone, Copy)]
105pub struct IssuedValue {
106 mantissa: i64,
108 exponent: i8,
109}
110
111impl IssuedValue {
112 pub fn from_mantissa_exponent(mantissa: i64, exponent: i8) -> Result<Self, Error> {
116 Self { mantissa, exponent }.normalize()
117 }
118
119 pub fn zero() -> Self {
121 Self {
122 mantissa: 0,
123 exponent: 0,
124 }
125 }
126
127 pub fn mantissa(&self) -> i64 {
129 self.mantissa
130 }
131
132 pub fn exponent(&self) -> i8 {
134 self.exponent
135 }
136
137 fn normalize(self) -> Result<Self, Error> {
139 const MANTISSA_MIN: i64 = 1000000000000000;
142 const MANTISSA_MAX: i64 = 9999999999999999;
143 const EXPONENT_MIN: i8 = -96;
144 const EXPONENT_MAX: i8 = 80;
145
146 let mut exponent = self.exponent;
147 let (mut mantissa, negative) = match self.mantissa {
148 0 => {
149 return Ok(Self::zero());
150 }
151 1.. => (self.mantissa, false),
152 ..=-1 => (
153 self.mantissa.checked_neg().ok_or_else(|| {
154 Error::OutOfRange("Specified mantissa cannot be i64::MIN".to_string())
155 })?,
156 true,
157 ),
158 };
159
160 while mantissa < MANTISSA_MIN && exponent > EXPONENT_MIN {
161 mantissa *= 10;
162 exponent -= 1;
163 }
164
165 while mantissa > MANTISSA_MAX && exponent < EXPONENT_MAX {
166 mantissa /= 10;
167 exponent += 1;
168 }
169
170 if mantissa > MANTISSA_MAX || exponent > EXPONENT_MAX {
171 return Err(Error::OutOfRange(format!(
172 "Issued value too big to be normalized: {:?}",
173 self
174 )));
175 }
176
177 if mantissa < MANTISSA_MIN || exponent < EXPONENT_MIN {
178 return Ok(Self::zero());
179 }
180
181 if negative {
182 mantissa = -mantissa;
183 }
184
185 Ok(Self { mantissa, exponent })
186 }
187}
188
189#[cfg(test)]
190mod test {
191 use super::*;
192 use ascii::AsciiChar;
193 use assert_matches::assert_matches;
194
195 #[test]
196 fn test_drops_amount() {
197 let amount = DropsAmount::from_drops(0).unwrap();
198 assert_eq!(amount.drops(), 0);
199 let amount = DropsAmount::from_drops(10000).unwrap();
200 assert_eq!(amount.drops(), 10000);
201 let amount = DropsAmount::from_drops(u64::MAX >> 2).unwrap();
203 assert_eq!(amount.drops(), u64::MAX >> 2);
204 }
205
206 #[test]
209 fn test_drops_amount_out_of_range() {
210 let result = DropsAmount::from_drops(1 << 62);
211 assert_matches!(result, Err(Error::OutOfRange(message)) => {
212 assert!(message.contains("Drop amounts cannot use the two must significant bits"), "message: {}", message);
213 });
214 let result = DropsAmount::from_drops(1 << 63);
215 assert_matches!(result, Err(Error::OutOfRange(message)) => {
216 assert!(message.contains("Drop amounts cannot use the two must significant bits"), "message: {}", message);
217 });
218 }
219
220 #[test]
222 fn test_issued_amount_xrp() {
223 let result = IssuedAmount::from_issued_value(
224 IssuedValue::from_mantissa_exponent(1, 0).unwrap(),
225 CurrencyCode::xrp(),
226 AccountId::from_address("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn").unwrap(),
227 );
228 assert_matches!(result, Err(Error::InvalidData(message)) => {
229 assert!(message.contains("Issued amount cannot have XRP currency code"), "message: {}", message);
230 });
231 }
232
233 #[test]
235 fn test_issued_amount_standard() {
236 IssuedAmount::from_issued_value(
237 IssuedValue::from_mantissa_exponent(1, 0).unwrap(),
238 CurrencyCode::standard([AsciiChar::U, AsciiChar::S, AsciiChar::D]).unwrap(),
239 AccountId::from_address("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn").unwrap(),
240 )
241 .unwrap();
242 }
243
244 #[test]
246 fn test_issued_amount_non_standard() {
247 IssuedAmount::from_issued_value(
248 IssuedValue::from_mantissa_exponent(1, 0).unwrap(),
249 CurrencyCode::non_standard([
250 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
251 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
252 ])
253 .unwrap(),
254 AccountId::from_address("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn").unwrap(),
255 )
256 .unwrap();
257 }
258
259 #[test]
260 fn test_issued_value_zero() {
261 let value = IssuedValue::from_mantissa_exponent(0, 0).unwrap();
262 assert_eq!(value.mantissa(), 0);
263 assert_eq!(value.exponent(), 0);
264 }
265
266 #[test]
267 fn test_issued_value_one() {
268 let value = IssuedValue::from_mantissa_exponent(1, 0).unwrap();
269 assert_eq!(value.mantissa(), 1_000_000_000_000_000);
270 assert_eq!(value.exponent(), -15);
271 }
272
273 #[test]
274 fn test_issued_value_minus_one() {
275 let value = IssuedValue::from_mantissa_exponent(-1, 0).unwrap();
276 assert_eq!(value.mantissa(), -1_000_000_000_000_000);
277 assert_eq!(value.exponent(), -15);
278 }
279
280 #[test]
282 fn test_issued_value_zero_normalize_exponent() {
283 let value = IssuedValue::from_mantissa_exponent(0, 10).unwrap();
284 assert_eq!(value.mantissa(), 0);
285 assert_eq!(value.exponent(), 0);
286 }
287
288 #[test]
290 fn test_issued_value_scale_up() {
291 let value = IssuedValue::from_mantissa_exponent(123, 0).unwrap();
292 assert_eq!(value.mantissa(), 1_230_000_000_000_000);
293 assert_eq!(value.exponent(), -13);
294 }
295
296 #[test]
298 fn test_issued_value_scale_down() {
299 let value = IssuedValue::from_mantissa_exponent(1_230_000_000_000_000_000, 0).unwrap();
300 assert_eq!(value.mantissa(), 1_230_000_000_000_000);
301 assert_eq!(value.exponent(), 3);
302 }
303
304 #[test]
306 fn test_issued_value_negative() {
307 let value = IssuedValue::from_mantissa_exponent(-123, 0).unwrap();
308 assert_eq!(value.mantissa(), -1_230_000_000_000_000);
309 assert_eq!(value.exponent(), -13);
310 }
311
312 #[test]
314 fn test_issued_value_mantissa_min_scale_up() {
315 let value = IssuedValue::from_mantissa_exponent(1, 0).unwrap();
316 assert_eq!(value.mantissa(), 1_000_000_000_000_000);
317 assert_eq!(value.exponent(), -15);
318 }
319
320 #[test]
322 fn test_issued_value_mantissa_min_scale_down() {
323 let value = IssuedValue::from_mantissa_exponent(1_000_000_000_000_000_000, 0).unwrap();
324 assert_eq!(value.mantissa(), 1_000_000_000_000_000);
325 assert_eq!(value.exponent(), 3);
326 }
327
328 #[test]
330 fn test_issued_value_mantissa_max_scale_down() {
331 let value = IssuedValue::from_mantissa_exponent(999_999_999_999_999_900, 0).unwrap();
332 assert_eq!(value.mantissa(), 9_999_999_999_999_999);
333 assert_eq!(value.exponent(), 2);
334 }
335
336 #[test]
338 fn test_issued_value_exponent_max() {
339 let value = IssuedValue::from_mantissa_exponent(1_230_000_000_000_000_000, 77).unwrap();
340 assert_eq!(value.mantissa(), 1_230_000_000_000_000);
341 assert_eq!(value.exponent(), 80);
342 }
343
344 #[test]
346 fn test_issued_value_out_of_range_too_big_mantissa() {
347 let result = IssuedValue::from_mantissa_exponent(1_000_000_000_000_000_000, 78);
348 assert_matches!(result, Err(Error::OutOfRange(message)) => {
349 assert!(message.contains("Issued value too big to be normalized"), "message: {}", message);
350 });
351 }
352
353 #[test]
355 fn test_issued_value_out_of_range_too_big_exponent() {
356 let result = IssuedValue::from_mantissa_exponent(1_000_000_000_000_000, 81);
357 assert_matches!(result, Err(Error::OutOfRange(message)) => {
358 assert!(message.contains("Issued value too big to be normalized"), "message: {}", message);
359 });
360 }
361
362 #[test]
364 fn test_issued_value_exponent_min() {
365 let value = IssuedValue::from_mantissa_exponent(123_000_000_000, -92).unwrap();
366 assert_eq!(value.mantissa(), 1_230_000_000_000_000);
367 assert_eq!(value.exponent(), -96);
368 }
369
370 #[test]
373 fn test_issued_value_non_zero_normalized_to_zero_mantissa_too_small() {
374 let value = IssuedValue::from_mantissa_exponent(123_000_000_000, -93).unwrap();
375 assert_eq!(value.mantissa(), 0);
376 assert_eq!(value.exponent(), 0);
377 }
378
379 #[test]
382 fn test_issued_value_non_zero_normalized_to_zero_exponent_too_small() {
383 let value = IssuedValue::from_mantissa_exponent(1_230_000_000_000_000, -97).unwrap();
384 assert_eq!(value.mantissa(), 0);
385 assert_eq!(value.exponent(), 0);
386 }
387
388 #[test]
390 fn test_issued_value_mantissa_i64_min() {
391 let result = IssuedValue::from_mantissa_exponent(i64::MIN, 0);
392 assert_matches!(result, Err(Error::OutOfRange(message)) => {
393 assert!(message.contains("Specified mantissa cannot be i64::MIN"), "message: {}", message);
394 });
395 }
396
397 #[test]
398 fn test_amount_drops() {
399 let amount = Amount::drops(0).unwrap();
400 assert!(amount.is_drops());
401 }
402
403 #[test]
404 fn test_amount_issued() {
405 let amount = Amount::issued(
406 IssuedValue::from_mantissa_exponent(1, 0).unwrap(),
407 CurrencyCode::standard([AsciiChar::U, AsciiChar::S, AsciiChar::D]).unwrap(),
408 AccountId::from_address("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn").unwrap(),
409 )
410 .unwrap();
411 assert!(amount.is_issued());
412 }
413}