1use std::{fmt, ops};
2
3use cosmwasm_schema::cw_serde;
4use cosmwasm_std::{Coin, OverflowError, OverflowOperation, StdResult};
5
6#[cw_serde]
9#[derive(Default)]
10pub struct NativeBalance(pub Vec<Coin>);
11
12impl NativeBalance {
13 pub fn into_vec(self) -> Vec<Coin> {
14 self.0
15 }
16
17 pub fn has(&self, required: &Coin) -> bool {
19 self.0
20 .iter()
21 .find(|c| c.denom == required.denom)
22 .map(|m| m.amount >= required.amount)
23 .unwrap_or(false)
24 }
25
26 pub fn normalize(&mut self) {
28 self.0.retain(|c| !c.amount.is_zero());
30 self.0.sort_unstable_by(|a, b| a.denom.cmp(&b.denom));
32
33 let mut dups: Vec<usize> = self
35 .0
36 .iter()
37 .enumerate()
38 .filter_map(|(i, c)| {
39 if i != 0 && c.denom == self.0[i - 1].denom {
40 Some(i)
41 } else {
42 None
43 }
44 })
45 .collect();
46 dups.reverse();
47
48 for dup in dups {
50 let add = self.0[dup].amount;
51 self.0[dup - 1].amount += add;
52 self.0.remove(dup);
53 }
54 }
55
56 fn find(&self, denom: &str) -> Option<(usize, &Coin)> {
57 self.0.iter().enumerate().find(|(_i, c)| c.denom == denom)
58 }
59
60 fn insert_pos(&self, denom: &str) -> Option<usize> {
64 self.0.iter().position(|c| c.denom.as_str() >= denom)
65 }
66
67 pub fn is_empty(&self) -> bool {
68 !self.0.iter().any(|x| !x.amount.is_zero())
69 }
70
71 pub fn sub_saturating(mut self, other: Coin) -> StdResult<Self> {
73 match self.find(&other.denom) {
74 Some((i, c)) => {
75 if c.amount <= other.amount {
76 self.0.remove(i);
77 } else {
78 self.0[i].amount = self.0[i].amount.checked_sub(other.amount)?;
79 }
80 }
81 None => return Err(OverflowError::new(OverflowOperation::Sub).into()),
83 };
84 Ok(self)
85 }
86}
87
88impl fmt::Display for NativeBalance {
89 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
90 for c in &self.0 {
91 write!(f, "{}{}", c.denom, c.amount)?
92 }
93 Ok(())
94 }
95}
96
97impl ops::AddAssign<Coin> for NativeBalance {
98 fn add_assign(&mut self, other: Coin) {
99 match self.find(&other.denom) {
100 Some((i, c)) => {
101 self.0[i].amount = c.amount + other.amount;
102 }
103 None => match self.insert_pos(&other.denom) {
105 Some(idx) => self.0.insert(idx, other),
106 None => self.0.push(other),
107 },
108 };
109 }
110}
111
112impl ops::Add<Coin> for NativeBalance {
113 type Output = Self;
114
115 fn add(mut self, other: Coin) -> Self {
116 self += other;
117 self
118 }
119}
120
121impl ops::AddAssign<NativeBalance> for NativeBalance {
122 fn add_assign(&mut self, other: NativeBalance) {
123 for coin in other.0.into_iter() {
124 self.add_assign(coin);
125 }
126 }
127}
128
129impl ops::Add<NativeBalance> for NativeBalance {
130 type Output = Self;
131
132 fn add(mut self, other: NativeBalance) -> Self {
133 self += other;
134 self
135 }
136}
137
138impl ops::Sub<Coin> for NativeBalance {
139 type Output = StdResult<Self>;
140
141 fn sub(mut self, other: Coin) -> StdResult<Self> {
142 match self.find(&other.denom) {
143 Some((i, c)) => {
144 let remainder = c.amount.checked_sub(other.amount)?;
145 if remainder.is_zero() {
146 self.0.remove(i);
147 } else {
148 self.0[i].amount = remainder;
149 }
150 }
151 None => return Err(OverflowError::new(OverflowOperation::Sub).into()),
153 };
154 Ok(self)
155 }
156}
157
158impl ops::Sub<Vec<Coin>> for NativeBalance {
159 type Output = StdResult<Self>;
160
161 fn sub(self, amount: Vec<Coin>) -> StdResult<Self> {
162 let mut res = self;
163 for coin in amount {
164 res = res.sub(coin.clone())?;
165 }
166 Ok(res)
167 }
168}
169
170#[cfg(test)]
171mod test {
172 use super::*;
173 use cosmwasm_std::coin;
174
175 #[test]
176 fn balance_has_works() {
177 let balance = NativeBalance(vec![coin(555, "BTC"), coin(12345, "ETH")]);
178
179 assert!(balance.has(&coin(777, "ETH")));
181 assert!(balance.has(&coin(555, "BTC")));
183
184 assert!(!balance.has(&coin(12346, "ETH")));
186 assert!(!balance.has(&coin(456, "ETC")));
188 }
189
190 #[test]
191 fn balance_add_works() {
192 let balance = NativeBalance(vec![coin(555, "BTC"), coin(12345, "ETH")]);
193
194 let more_eth = balance.clone() + coin(54321, "ETH");
196 assert_eq!(
197 more_eth,
198 NativeBalance(vec![coin(555, "BTC"), coin(66666, "ETH")])
199 );
200
201 let add_atom = balance + coin(777, "ATOM");
203 assert_eq!(
204 add_atom,
205 NativeBalance(vec![
206 coin(777, "ATOM"),
207 coin(555, "BTC"),
208 coin(12345, "ETH"),
209 ])
210 );
211 }
212
213 #[test]
214 fn balance_in_place_addition() {
215 let mut balance = NativeBalance(vec![coin(555, "BTC")]);
216 balance += coin(777, "ATOM");
217 assert_eq!(
218 &balance,
219 &NativeBalance(vec![coin(777, "ATOM"), coin(555, "BTC")])
220 );
221
222 balance += NativeBalance(vec![coin(666, "ETH"), coin(123, "ATOM")]);
223 assert_eq!(
224 &balance,
225 &NativeBalance(vec![coin(900, "ATOM"), coin(555, "BTC"), coin(666, "ETH")])
226 );
227
228 let sum = balance + NativeBalance(vec![coin(234, "BTC")]);
229 assert_eq!(
230 sum,
231 NativeBalance(vec![coin(900, "ATOM"), coin(789, "BTC"), coin(666, "ETH")])
232 );
233 }
234
235 #[test]
236 fn balance_subtract_works() {
237 let balance = NativeBalance(vec![coin(555, "BTC"), coin(12345, "ETH")]);
238
239 let less_eth = (balance.clone() - coin(2345, "ETH")).unwrap();
241 assert_eq!(
242 less_eth,
243 NativeBalance(vec![coin(555, "BTC"), coin(10000, "ETH")])
244 );
245
246 let no_btc = (balance.clone() - coin(555, "BTC")).unwrap();
249 assert_eq!(no_btc, NativeBalance(vec![coin(12345, "ETH")]));
250
251 let underflow = balance.clone() - coin(666, "BTC");
253 assert!(underflow.is_err());
254
255 let missing = balance - coin(1, "ATOM");
257 assert!(missing.is_err());
258 }
259
260 #[test]
261 fn balance_subtract_saturating_works() {
262 let balance = NativeBalance(vec![coin(555, "BTC"), coin(12345, "ETH")]);
263
264 let less_eth = balance.clone().sub_saturating(coin(2345, "ETH")).unwrap();
266 assert_eq!(
267 less_eth,
268 NativeBalance(vec![coin(555, "BTC"), coin(10000, "ETH")])
269 );
270
271 let no_btc = balance.clone().sub_saturating(coin(555, "BTC")).unwrap();
274 assert_eq!(no_btc, NativeBalance(vec![coin(12345, "ETH")]));
275
276 let saturating = balance.clone().sub_saturating(coin(666, "BTC"));
278 assert!(saturating.is_ok());
279 assert_eq!(saturating.unwrap(), NativeBalance(vec![coin(12345, "ETH")]));
280
281 let missing = balance - coin(1, "ATOM");
283 assert!(missing.is_err());
284 }
285
286 #[test]
287 fn normalize_balance() {
288 let mut balance = NativeBalance(vec![coin(123, "ETH"), coin(0, "BTC"), coin(8990, "ATOM")]);
290 balance.normalize();
291 assert_eq!(
292 balance,
293 NativeBalance(vec![coin(8990, "ATOM"), coin(123, "ETH")])
294 );
295
296 let mut balance = NativeBalance(vec![
298 coin(123, "ETH"),
299 coin(789, "BTC"),
300 coin(321, "ETH"),
301 coin(11, "BTC"),
302 ]);
303 balance.normalize();
304 assert_eq!(
305 balance,
306 NativeBalance(vec![coin(800, "BTC"), coin(444, "ETH")])
307 );
308 }
309}