1use alloc::collections::BTreeMap;
2use core::fmt;
3use core::str::FromStr;
4
5use crate::prelude::*;
6use crate::{Coin, CoinsError, OverflowError, OverflowOperation, StdError, StdResult, Uint128};
7
8#[derive(Clone, Default, Debug, PartialEq, Eq)]
18pub struct Coins(BTreeMap<String, Coin>);
19
20impl TryFrom<Vec<Coin>> for Coins {
25 type Error = CoinsError;
26
27 fn try_from(vec: Vec<Coin>) -> Result<Self, CoinsError> {
28 let mut map = BTreeMap::new();
29 for coin in vec {
30 if coin.amount.is_zero() {
31 continue;
32 }
33
34 if map.insert(coin.denom.clone(), coin).is_some() {
36 return Err(CoinsError::DuplicateDenom);
37 }
38 }
39
40 Ok(Self(map))
41 }
42}
43
44impl TryFrom<&[Coin]> for Coins {
45 type Error = CoinsError;
46
47 fn try_from(slice: &[Coin]) -> Result<Self, CoinsError> {
48 slice.to_vec().try_into()
49 }
50}
51
52impl From<Coin> for Coins {
53 fn from(value: Coin) -> Self {
54 let mut coins = Coins::default();
55 coins.add(value).unwrap();
57 coins
58 }
59}
60
61impl<const N: usize> TryFrom<[Coin; N]> for Coins {
62 type Error = CoinsError;
63
64 fn try_from(slice: [Coin; N]) -> Result<Self, CoinsError> {
65 slice.to_vec().try_into()
66 }
67}
68
69impl From<Coins> for Vec<Coin> {
70 fn from(value: Coins) -> Self {
71 value.into_vec()
72 }
73}
74
75impl FromStr for Coins {
76 type Err = StdError;
77
78 fn from_str(s: &str) -> StdResult<Self> {
79 if s.is_empty() {
80 return Ok(Self::default());
81 }
82
83 Ok(s.split(',')
84 .map(Coin::from_str)
85 .collect::<Result<Vec<_>, _>>()?
86 .try_into()?)
87 }
88}
89
90impl fmt::Display for Coins {
91 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92 let s = self
93 .0
94 .values()
95 .map(|coin| coin.to_string())
96 .collect::<Vec<_>>()
97 .join(",");
98 write!(f, "{s}")
99 }
100}
101
102impl Coins {
103 pub fn to_vec(&self) -> Vec<Coin> {
108 self.0.values().cloned().collect()
109 }
110
111 pub fn into_vec(self) -> Vec<Coin> {
116 self.0.into_values().collect()
117 }
118
119 pub fn len(&self) -> usize {
121 self.0.len()
122 }
123
124 pub fn is_empty(&self) -> bool {
126 self.0.is_empty()
127 }
128
129 pub fn denoms(&self) -> Vec<String> {
132 self.0.keys().cloned().collect()
133 }
134
135 pub fn amount_of(&self, denom: &str) -> Uint128 {
137 self.0
138 .get(denom)
139 .map(|c| c.amount)
140 .unwrap_or_else(Uint128::zero)
141 }
142
143 pub fn contains_only(&self, denom: &str) -> Option<Uint128> {
163 if self.len() == 1 {
164 self.0.get(denom).map(|c| c.amount)
165 } else {
166 None
167 }
168 }
169
170 pub fn add(&mut self, coin: Coin) -> StdResult<()> {
173 if coin.amount.is_zero() {
174 return Ok(());
175 }
176
177 match self.0.get_mut(&coin.denom) {
179 None => {
180 self.0.insert(coin.denom.clone(), coin);
181 }
182 Some(existing) => {
183 existing.amount = existing.amount.checked_add(coin.amount)?;
184 }
185 }
186 Ok(())
187 }
188
189 pub fn sub(&mut self, coin: Coin) -> StdResult<()> {
192 match self.0.get_mut(&coin.denom) {
193 Some(existing) => {
194 existing.amount = existing.amount.checked_sub(coin.amount)?;
195 if existing.amount.is_zero() {
197 self.0.remove(&coin.denom);
198 }
199 }
200 None => {
201 if coin.amount.is_zero() {
203 return Ok(());
204 }
205 return Err(OverflowError::new(OverflowOperation::Sub).into());
206 }
207 }
208
209 Ok(())
210 }
211
212 pub fn iter(&self) -> CoinsIter<'_> {
234 CoinsIter(self.0.iter())
235 }
236}
237
238impl IntoIterator for Coins {
239 type Item = Coin;
240 type IntoIter = CoinsIntoIter;
241
242 fn into_iter(self) -> Self::IntoIter {
243 CoinsIntoIter(self.0.into_iter())
244 }
245}
246
247impl<'a> IntoIterator for &'a Coins {
248 type Item = &'a Coin;
249 type IntoIter = CoinsIter<'a>;
250
251 fn into_iter(self) -> Self::IntoIter {
252 self.iter()
253 }
254}
255
256#[derive(Debug)]
257pub struct CoinsIntoIter(alloc::collections::btree_map::IntoIter<String, Coin>);
258
259impl Iterator for CoinsIntoIter {
260 type Item = Coin;
261
262 fn next(&mut self) -> Option<Self::Item> {
263 self.0.next().map(|(_, coin)| coin)
264 }
265
266 fn size_hint(&self) -> (usize, Option<usize>) {
267 self.0.size_hint()
269 }
270}
271
272impl DoubleEndedIterator for CoinsIntoIter {
273 fn next_back(&mut self) -> Option<Self::Item> {
274 self.0.next_back().map(|(_, coin)| coin)
275 }
276}
277
278impl ExactSizeIterator for CoinsIntoIter {
279 fn len(&self) -> usize {
280 self.0.len()
281 }
282}
283
284#[derive(Debug)]
285pub struct CoinsIter<'a>(alloc::collections::btree_map::Iter<'a, String, Coin>);
286
287impl<'a> Iterator for CoinsIter<'a> {
288 type Item = &'a Coin;
289
290 fn next(&mut self) -> Option<Self::Item> {
291 self.0.next().map(|(_, coin)| coin)
292 }
293
294 fn size_hint(&self) -> (usize, Option<usize>) {
295 self.0.size_hint()
297 }
298}
299
300impl<'a> DoubleEndedIterator for CoinsIter<'a> {
301 fn next_back(&mut self) -> Option<Self::Item> {
302 self.0.next_back().map(|(_, coin)| coin)
303 }
304}
305
306impl<'a> ExactSizeIterator for CoinsIter<'a> {
307 fn len(&self) -> usize {
308 self.0.len()
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315 use crate::coin;
316
317 fn sort_by_denom(vec: &mut [Coin]) {
319 vec.sort_by(|a, b| a.denom.cmp(&b.denom));
320 }
321
322 fn mock_vec() -> Vec<Coin> {
324 vec![
325 coin(12345, "uatom"),
326 coin(69420, "ibc/1234ABCD"),
327 coin(88888, "factory/osmo1234abcd/subdenom"),
328 ]
329 }
330
331 fn mock_coins() -> Coins {
333 let mut coins = Coins::default();
334 for coin in mock_vec() {
335 coins.add(coin).unwrap();
336 }
337 coins
338 }
339
340 #[test]
341 fn converting_vec() {
342 let mut vec = mock_vec();
343 let coins = mock_coins();
344
345 assert_eq!(Coins::try_from(vec.as_slice()).unwrap(), coins);
347 assert_eq!(Coins::try_from(vec.clone()).unwrap(), coins);
349
350 sort_by_denom(&mut vec);
351
352 assert_eq!(coins.to_vec(), vec);
355 assert_eq!(coins.into_vec(), vec);
358 }
359
360 #[test]
361 fn converting_str() {
362 let s1 = "88888factory/osmo1234abcd/subdenom,12345uatom,69420ibc/1234ABCD";
364 let s2 = "88888factory/osmo1234abcd/subdenom,69420ibc/1234ABCD,12345uatom";
366
367 let invalid = "12345uatom,noamount";
368
369 let coins = mock_coins();
370
371 assert_eq!(Coins::from_str(s1).unwrap(), coins);
374 assert_eq!(Coins::from_str(s2).unwrap(), coins);
375 assert_eq!(Coins::from_str("").unwrap(), Coins::default());
376
377 assert_eq!(coins.to_string(), s2);
380 assert_eq!(Coins::default().to_string(), "");
381 assert_eq!(
382 Coins::from_str(invalid).unwrap_err().to_string(),
383 "Generic error: Parsing Coin: Missing amount or non-digit characters in amount"
384 );
385 }
386
387 #[test]
388 fn handling_duplicates() {
389 let mut vec = mock_vec();
391 vec.push(coin(67890, "uatom"));
392
393 let err = Coins::try_from(vec).unwrap_err();
394 assert_eq!(err, CoinsError::DuplicateDenom);
395 }
396
397 #[test]
398 fn handling_zero_amount() {
399 let mut vec = mock_vec();
401 vec[0].amount = Uint128::zero();
402
403 let coins = Coins::try_from(vec).unwrap();
404 assert_eq!(coins.len(), 2);
405 assert_ne!(coins.amount_of("ibc/1234ABCD"), Uint128::zero());
406 assert_ne!(
407 coins.amount_of("factory/osmo1234abcd/subdenom"),
408 Uint128::zero()
409 );
410
411 let mut coins = Coins::default();
413 coins.add(coin(0, "uusd")).unwrap();
414 assert!(coins.is_empty());
415 }
416
417 #[test]
418 fn length() {
419 let coins = Coins::default();
420 assert_eq!(coins.len(), 0);
421 assert!(coins.is_empty());
422
423 let coins = mock_coins();
424 assert_eq!(coins.len(), 3);
425 assert!(!coins.is_empty());
426 }
427
428 #[test]
429 fn add_coin() {
430 let mut coins = mock_coins();
431
432 coins.add(coin(12345, "uatom")).unwrap();
434 assert_eq!(coins.len(), 3);
435 assert_eq!(coins.amount_of("uatom").u128(), 24690);
436
437 coins.add(coin(123, "uusd")).unwrap();
439 assert_eq!(coins.len(), 4);
440
441 coins.add(coin(0, "uusd")).unwrap();
443 assert_eq!(coins.amount_of("uusd").u128(), 123);
444
445 coins.add(coin(0, "utest")).unwrap();
447 assert_eq!(coins.len(), 4);
448 }
449
450 #[test]
451 fn sub_coins() {
452 let mut coins: Coins = coin(12345, "uatom").into();
453
454 let err = coins.sub(coin(12346, "uatom")).unwrap_err();
456 assert!(matches!(err, StdError::Overflow { .. }));
457
458 let err = coins.sub(coin(12345, "uusd")).unwrap_err();
460 assert!(matches!(err, StdError::Overflow { .. }));
461
462 coins.sub(coin(1, "uatom")).unwrap();
464 assert_eq!(coins.len(), 1);
465 assert_eq!(coins.amount_of("uatom").u128(), 12344);
466
467 coins.sub(coin(12344, "uatom")).unwrap();
469 assert!(coins.is_empty());
470
471 coins.sub(coin(0, "uusd")).unwrap();
473 assert!(coins.is_empty());
474 let mut coins: Coins = coin(12345, "uatom").into();
475
476 coins.sub(coin(0, "uatom")).unwrap();
478 assert_eq!(coins.len(), 1);
479 assert_eq!(coins.amount_of("uatom").u128(), 12345);
480 }
481
482 #[test]
483 fn coin_to_coins() {
484 let coins: Coins = coin(0, "uusd").into();
486 assert!(coins.is_empty());
487
488 let coins = Coins::from(coin(12345, "uatom"));
490 assert_eq!(coins.len(), 1);
491 assert_eq!(coins.amount_of("uatom").u128(), 12345);
492 }
493
494 #[test]
495 fn exact_size_iterator() {
496 let coins = mock_coins();
497 let iter = coins.iter();
498 assert_eq!(iter.len(), 3);
499 assert_eq!(iter.size_hint(), (3, Some(3)));
500
501 let iter = coins.into_iter();
502 assert_eq!(iter.len(), 3);
503 assert_eq!(iter.size_hint(), (3, Some(3)));
504 }
505
506 #[test]
507 fn can_iterate_owned() {
508 let coins = mock_coins();
509 let mut moved = Coins::default();
510 for c in coins {
511 moved.add(c).unwrap();
512 }
513 assert_eq!(moved.len(), 3);
514
515 assert!(mock_coins().into_iter().eq(mock_coins().to_vec()));
516 }
517
518 #[test]
519 fn can_iterate_borrowed() {
520 let coins = mock_coins();
521 assert!(coins
522 .iter()
523 .map(|c| &c.denom)
524 .eq(coins.to_vec().iter().map(|c| &c.denom)));
525
526 assert_eq!(coins.amount_of("uatom").u128(), 12345);
528 }
529}