cosmwasm_std/
coins.rs

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/// A collection of coins, similar to Cosmos SDK's `sdk.Coins` struct.
9///
10/// Differently from `sdk.Coins`, which is a vector of `sdk.Coin`, here we
11/// implement Coins as a BTreeMap that maps from coin denoms to `Coin`.
12/// This has a number of advantages:
13///
14/// - coins are naturally sorted alphabetically by denom
15/// - duplicate denoms are automatically removed
16/// - cheaper for searching/inserting/deleting: O(log(n)) compared to O(n)
17#[derive(Clone, Default, Debug, PartialEq, Eq)]
18pub struct Coins(BTreeMap<String, Coin>);
19
20/// Casting a Vec<Coin> to Coins.
21/// The Vec can be out of order, but must not contain duplicate denoms.
22/// If you want to sum up duplicates, create an empty instance using `Coins::default` and
23/// use `Coins::add` to add your coins.
24impl 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 the insertion returns a previous value, we have a duplicate denom
35            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        // this can never overflow (because there are no coins in there yet), so we can unwrap
56        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    /// Conversion to Vec<Coin>, while NOT consuming the original object.
104    ///
105    /// This produces a vector of coins that is sorted alphabetically by denom with
106    /// no duplicate denoms.
107    pub fn to_vec(&self) -> Vec<Coin> {
108        self.0.values().cloned().collect()
109    }
110
111    /// Conversion to Vec<Coin>, consuming the original object.
112    ///
113    /// This produces a vector of coins that is sorted alphabetically by denom with
114    /// no duplicate denoms.
115    pub fn into_vec(self) -> Vec<Coin> {
116        self.0.into_values().collect()
117    }
118
119    /// Returns the number of different denoms in this collection.
120    pub fn len(&self) -> usize {
121        self.0.len()
122    }
123
124    /// Returns `true` if this collection contains no coins.
125    pub fn is_empty(&self) -> bool {
126        self.0.is_empty()
127    }
128
129    /// Returns the denoms as a vector of strings.
130    /// The vector is guaranteed to not contain duplicates and sorted alphabetically.
131    pub fn denoms(&self) -> Vec<String> {
132        self.0.keys().cloned().collect()
133    }
134
135    /// Returns the amount of the given denom or zero if the denom is not present.
136    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    /// Returns the amount of the given denom if and only if this collection contains only
144    /// the given denom. Otherwise `None` is returned.
145    ///
146    /// # Examples
147    ///
148    /// ```rust
149    /// use cosmwasm_std::{Coin, Coins, coin};
150    ///
151    /// let coins: Coins = [coin(100, "uatom")].try_into().unwrap();
152    /// assert_eq!(coins.contains_only("uatom").unwrap().u128(), 100);
153    /// assert_eq!(coins.contains_only("uluna"), None);
154    /// ```
155    ///
156    /// ```rust
157    /// use cosmwasm_std::{Coin, Coins, coin};
158    ///
159    /// let coins: Coins = [coin(100, "uatom"), coin(200, "uusd")].try_into().unwrap();
160    /// assert_eq!(coins.contains_only("uatom"), None);
161    /// ```
162    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    /// Adds the given coin to this `Coins` instance.
171    /// Errors in case of overflow.
172    pub fn add(&mut self, coin: Coin) -> StdResult<()> {
173        if coin.amount.is_zero() {
174            return Ok(());
175        }
176
177        // if the coin is not present yet, insert it, otherwise add to existing amount
178        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    /// Subtracts the given coin from this `Coins` instance.
190    /// Errors in case of overflow or if the denom is not present.
191    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                // make sure to remove zero coin
196                if existing.amount.is_zero() {
197                    self.0.remove(&coin.denom);
198                }
199            }
200            None => {
201                // ignore zero subtraction
202                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    /// Returns an iterator over the coins.
213    ///
214    /// # Examples
215    ///
216    /// ```
217    /// # use cosmwasm_std::{coin, Coin, Coins, Uint128};
218    /// let mut coins = Coins::default();
219    /// coins.add(coin(500, "uluna")).unwrap();
220    /// coins.add(coin(1000, "uatom")).unwrap();
221    /// let mut iterator = coins.iter();
222    ///
223    /// let uatom = iterator.next().unwrap();
224    /// assert_eq!(uatom.denom, "uatom");
225    /// assert_eq!(uatom.amount.u128(), 1000);
226    ///
227    /// let uluna = iterator.next().unwrap();
228    /// assert_eq!(uluna.denom, "uluna");
229    /// assert_eq!(uluna.amount.u128(), 500);
230    ///
231    /// assert_eq!(iterator.next(), None);
232    /// ```
233    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        // Since btree_map::IntoIter implements ExactSizeIterator, this is guaranteed to return the exact length
268        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        // Since btree_map::Iter implements ExactSizeIterator, this is guaranteed to return the exact length
296        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    /// Sort a Vec<Coin> by denom alphabetically
318    fn sort_by_denom(vec: &mut [Coin]) {
319        vec.sort_by(|a, b| a.denom.cmp(&b.denom));
320    }
321
322    /// Returns a mockup Vec<Coin>. In this example, the coins are not in order
323    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    /// Return a mockup Coins that contains the same coins as in `mock_vec`
332    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        // &[Coin] --> Coins
346        assert_eq!(Coins::try_from(vec.as_slice()).unwrap(), coins);
347        // Vec<Coin> --> Coins
348        assert_eq!(Coins::try_from(vec.clone()).unwrap(), coins);
349
350        sort_by_denom(&mut vec);
351
352        // &Coins --> Vec<Coins>
353        // NOTE: the returned vec should be sorted
354        assert_eq!(coins.to_vec(), vec);
355        // Coins --> Vec<Coins>
356        // NOTE: the returned vec should be sorted
357        assert_eq!(coins.into_vec(), vec);
358    }
359
360    #[test]
361    fn converting_str() {
362        // not in order
363        let s1 = "88888factory/osmo1234abcd/subdenom,12345uatom,69420ibc/1234ABCD";
364        // in order
365        let s2 = "88888factory/osmo1234abcd/subdenom,69420ibc/1234ABCD,12345uatom";
366
367        let invalid = "12345uatom,noamount";
368
369        let coins = mock_coins();
370
371        // &str --> Coins
372        // NOTE: should generate the same Coins, regardless of input order
373        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        // Coins --> String
378        // NOTE: the generated string should be sorted
379        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        // create a Vec<Coin> that contains duplicate denoms
390        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        // create a Vec<Coin> that contains zero amounts
400        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        // adding a coin with zero amount should not be added
412        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        // existing denom
433        coins.add(coin(12345, "uatom")).unwrap();
434        assert_eq!(coins.len(), 3);
435        assert_eq!(coins.amount_of("uatom").u128(), 24690);
436
437        // new denom
438        coins.add(coin(123, "uusd")).unwrap();
439        assert_eq!(coins.len(), 4);
440
441        // zero amount
442        coins.add(coin(0, "uusd")).unwrap();
443        assert_eq!(coins.amount_of("uusd").u128(), 123);
444
445        // zero amount, new denom
446        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        // sub more than available
455        let err = coins.sub(coin(12346, "uatom")).unwrap_err();
456        assert!(matches!(err, StdError::Overflow { .. }));
457
458        // sub non-existent denom
459        let err = coins.sub(coin(12345, "uusd")).unwrap_err();
460        assert!(matches!(err, StdError::Overflow { .. }));
461
462        // partial sub
463        coins.sub(coin(1, "uatom")).unwrap();
464        assert_eq!(coins.len(), 1);
465        assert_eq!(coins.amount_of("uatom").u128(), 12344);
466
467        // full sub
468        coins.sub(coin(12344, "uatom")).unwrap();
469        assert!(coins.is_empty());
470
471        // sub zero, existing denom
472        coins.sub(coin(0, "uusd")).unwrap();
473        assert!(coins.is_empty());
474        let mut coins: Coins = coin(12345, "uatom").into();
475
476        // sub zero, non-existent denom
477        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        // zero coin results in empty collection
485        let coins: Coins = coin(0, "uusd").into();
486        assert!(coins.is_empty());
487
488        // happy path
489        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        // can still use the coins afterwards
527        assert_eq!(coins.amount_of("uatom").u128(), 12345);
528    }
529}