1use std::{fmt, ops};
2
3use cosmwasm_schema::cw_serde;
4use cosmwasm_std::{Coin, OverflowError, OverflowOperation, StdError, StdResult, Uint128};
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.u128() != 0);
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 != Uint128::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 => {
83 return Err(StdError::overflow(OverflowError::new(
84 OverflowOperation::Sub,
85 )))
86 }
87 };
88 Ok(self)
89 }
90}
91
92impl fmt::Display for NativeBalance {
93 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
94 for c in &self.0 {
95 write!(f, "{}{}", c.denom, c.amount)?
96 }
97 Ok(())
98 }
99}
100
101impl ops::AddAssign<Coin> for NativeBalance {
102 fn add_assign(&mut self, other: Coin) {
103 match self.find(&other.denom) {
104 Some((i, c)) => {
105 self.0[i].amount = c.amount + other.amount;
106 }
107 None => match self.insert_pos(&other.denom) {
109 Some(idx) => self.0.insert(idx, other),
110 None => self.0.push(other),
111 },
112 };
113 }
114}
115
116impl ops::Add<Coin> for NativeBalance {
117 type Output = Self;
118
119 fn add(mut self, other: Coin) -> Self {
120 self += other;
121 self
122 }
123}
124
125impl ops::AddAssign<NativeBalance> for NativeBalance {
126 fn add_assign(&mut self, other: NativeBalance) {
127 for coin in other.0.into_iter() {
128 self.add_assign(coin);
129 }
130 }
131}
132
133impl ops::Add<NativeBalance> for NativeBalance {
134 type Output = Self;
135
136 fn add(mut self, other: NativeBalance) -> Self {
137 self += other;
138 self
139 }
140}
141
142impl ops::Sub<Coin> for NativeBalance {
143 type Output = StdResult<Self>;
144
145 fn sub(mut self, other: Coin) -> StdResult<Self> {
146 match self.find(&other.denom) {
147 Some((i, c)) => {
148 let remainder = c.amount.checked_sub(other.amount)?;
149 if remainder.u128() == 0 {
150 self.0.remove(i);
151 } else {
152 self.0[i].amount = remainder;
153 }
154 }
155 None => {
157 return Err(StdError::overflow(OverflowError::new(
158 OverflowOperation::Sub,
159 )))
160 }
161 };
162 Ok(self)
163 }
164}
165
166impl ops::Sub<Vec<Coin>> for NativeBalance {
167 type Output = StdResult<Self>;
168
169 fn sub(self, amount: Vec<Coin>) -> StdResult<Self> {
170 let mut res = self;
171 for coin in amount {
172 res = res.sub(coin.clone())?;
173 }
174 Ok(res)
175 }
176}
177
178#[cfg(test)]
179mod test {
180 use super::*;
181 use cosmwasm_std::coin;
182
183 #[test]
184 fn balance_has_works() {
185 let balance = NativeBalance(vec![coin(555, "BTC"), coin(12345, "ETH")]);
186
187 assert!(balance.has(&coin(777, "ETH")));
189 assert!(balance.has(&coin(555, "BTC")));
191
192 assert!(!balance.has(&coin(12346, "ETH")));
194 assert!(!balance.has(&coin(456, "ETC")));
196 }
197
198 #[test]
199 fn balance_add_works() {
200 let balance = NativeBalance(vec![coin(555, "BTC"), coin(12345, "ETH")]);
201
202 let more_eth = balance.clone() + coin(54321, "ETH");
204 assert_eq!(
205 more_eth,
206 NativeBalance(vec![coin(555, "BTC"), coin(66666, "ETH")])
207 );
208
209 let add_atom = balance + coin(777, "ATOM");
211 assert_eq!(
212 add_atom,
213 NativeBalance(vec![
214 coin(777, "ATOM"),
215 coin(555, "BTC"),
216 coin(12345, "ETH"),
217 ])
218 );
219 }
220
221 #[test]
222 fn balance_in_place_addition() {
223 let mut balance = NativeBalance(vec![coin(555, "BTC")]);
224 balance += coin(777, "ATOM");
225 assert_eq!(
226 &balance,
227 &NativeBalance(vec![coin(777, "ATOM"), coin(555, "BTC")])
228 );
229
230 balance += NativeBalance(vec![coin(666, "ETH"), coin(123, "ATOM")]);
231 assert_eq!(
232 &balance,
233 &NativeBalance(vec![coin(900, "ATOM"), coin(555, "BTC"), coin(666, "ETH")])
234 );
235
236 let sum = balance + NativeBalance(vec![coin(234, "BTC")]);
237 assert_eq!(
238 sum,
239 NativeBalance(vec![coin(900, "ATOM"), coin(789, "BTC"), coin(666, "ETH")])
240 );
241 }
242
243 #[test]
244 fn balance_subtract_works() {
245 let balance = NativeBalance(vec![coin(555, "BTC"), coin(12345, "ETH")]);
246
247 let less_eth = (balance.clone() - coin(2345, "ETH")).unwrap();
249 assert_eq!(
250 less_eth,
251 NativeBalance(vec![coin(555, "BTC"), coin(10000, "ETH")])
252 );
253
254 let no_btc = (balance.clone() - coin(555, "BTC")).unwrap();
256 assert_eq!(no_btc, NativeBalance(vec![coin(12345, "ETH")]));
257
258 let underflow = balance.clone() - coin(666, "BTC");
260 assert!(underflow.is_err());
261
262 let missing = balance - coin(1, "ATOM");
264 assert!(missing.is_err());
265 }
266
267 #[test]
268 fn balance_subtract_saturating_works() {
269 let balance = NativeBalance(vec![coin(555, "BTC"), coin(12345, "ETH")]);
270
271 let less_eth = balance.clone().sub_saturating(coin(2345, "ETH")).unwrap();
273 assert_eq!(
274 less_eth,
275 NativeBalance(vec![coin(555, "BTC"), coin(10000, "ETH")])
276 );
277
278 let no_btc = balance.clone().sub_saturating(coin(555, "BTC")).unwrap();
280 assert_eq!(no_btc, NativeBalance(vec![coin(12345, "ETH")]));
281
282 let saturating = balance.clone().sub_saturating(coin(666, "BTC"));
284 assert!(saturating.is_ok());
285 assert_eq!(saturating.unwrap(), NativeBalance(vec![coin(12345, "ETH")]));
286
287 let missing = balance - coin(1, "ATOM");
289 assert!(missing.is_err());
290 }
291
292 #[test]
293 fn normalize_balance() {
294 let mut balance = NativeBalance(vec![coin(123, "ETH"), coin(0, "BTC"), coin(8990, "ATOM")]);
296 balance.normalize();
297 assert_eq!(
298 balance,
299 NativeBalance(vec![coin(8990, "ATOM"), coin(123, "ETH")])
300 );
301
302 let mut balance = NativeBalance(vec![
304 coin(123, "ETH"),
305 coin(789, "BTC"),
306 coin(321, "ETH"),
307 coin(11, "BTC"),
308 ]);
309 balance.normalize();
310 assert_eq!(
311 balance,
312 NativeBalance(vec![coin(800, "BTC"), coin(444, "ETH")])
313 );
314 }
315}