1use crate::app::CosmosRouter;
2use crate::error::{bail, AnyResult};
3use crate::executor::AppResponse;
4use crate::module::Module;
5use crate::prefixed_storage::typed_prefixed_storage::{
6 StoragePrefix, TypedPrefixedStorage, TypedPrefixedStorageMut,
7};
8use cosmwasm_std::{
9 coin, to_json_binary, Addr, AllBalanceResponse, Api, BalanceResponse, BankMsg, BankQuery,
10 Binary, BlockInfo, Coin, DenomMetadata, Event, Querier, Storage,
11};
12#[cfg(feature = "cosmwasm_1_3")]
13use cosmwasm_std::{AllDenomMetadataResponse, DenomMetadataResponse};
14#[cfg(feature = "cosmwasm_1_1")]
15use cosmwasm_std::{Order, StdResult, SupplyResponse, Uint128};
16use cw_storage_plus::Map;
17use cw_utils::NativeBalance;
18use itertools::Itertools;
19use schemars::JsonSchema;
20
21const BALANCES: Map<&Addr, NativeBalance> = Map::new("balances");
23
24const DENOM_METADATA: Map<String, DenomMetadata> = Map::new("metadata");
26
27#[derive(Clone, Debug, PartialEq, Eq, JsonSchema)]
29pub enum BankSudo {
30 Mint {
32 to_address: String,
34 amount: Vec<Coin>,
36 },
37}
38
39pub trait Bank: Module<ExecT = BankMsg, QueryT = BankQuery, SudoT = BankSudo> {}
45
46#[derive(Default)]
52pub struct BankKeeper {}
53
54impl StoragePrefix for BankKeeper {
55 const NAMESPACE: &'static [u8] = b"bank";
56}
57type BankStorage<'a> = TypedPrefixedStorage<'a, BankKeeper>;
58type BankStorageMut<'a> = TypedPrefixedStorageMut<'a, BankKeeper>;
59
60impl BankKeeper {
61 pub fn new() -> Self {
63 Self::default()
64 }
65
66 pub fn init_balance(
68 &self,
69 storage: &mut dyn Storage,
70 account: &Addr,
71 amount: Vec<Coin>,
72 ) -> AnyResult<()> {
73 let mut bank_storage = BankStorageMut::new(storage);
74 self.set_balance(&mut bank_storage, account, amount)
75 }
76
77 fn set_balance(
79 &self,
80 storage: &mut BankStorageMut,
81 account: &Addr,
82 amount: Vec<Coin>,
83 ) -> AnyResult<()> {
84 let mut balance = NativeBalance(amount);
85 balance.normalize();
86 BALANCES
87 .save(storage, account, &balance)
88 .map_err(Into::into)
89 }
90
91 pub fn set_denom_metadata(
93 &self,
94 storage: &mut dyn Storage,
95 denom: String,
96 metadata: DenomMetadata,
97 ) -> AnyResult<()> {
98 DENOM_METADATA
99 .save(storage, denom, &metadata)
100 .map_err(Into::into)
101 }
102
103 fn get_balance(&self, storage: &BankStorage, addr: &Addr) -> AnyResult<Vec<Coin>> {
105 let val = BALANCES.may_load(storage, addr)?;
106 Ok(val.unwrap_or_default().into_vec())
107 }
108
109 #[cfg(feature = "cosmwasm_1_1")]
110 fn get_supply(&self, storage: &BankStorage, denom: String) -> AnyResult<Coin> {
111 let supply: Uint128 = BALANCES
112 .range(storage, None, None, Order::Ascending)
113 .collect::<StdResult<Vec<_>>>()?
114 .into_iter()
115 .map(|a| a.1)
116 .fold(Uint128::zero(), |accum, item| {
117 let mut subtotal = Uint128::zero();
118 for coin in item.into_vec() {
119 if coin.denom == denom {
120 subtotal += coin.amount;
121 }
122 }
123 accum + subtotal
124 });
125 Ok(coin(supply.into(), denom))
126 }
127
128 fn send(
129 &self,
130 storage: &mut BankStorageMut,
131 from_address: Addr,
132 to_address: Addr,
133 amount: Vec<Coin>,
134 ) -> AnyResult<()> {
135 self.burn(storage, from_address, amount.clone())?;
136 self.mint(storage, to_address, amount)
137 }
138
139 fn mint(
140 &self,
141 storage: &mut BankStorageMut,
142 to_address: Addr,
143 amount: Vec<Coin>,
144 ) -> AnyResult<()> {
145 let amount = self.normalize_amount(amount)?;
146 let b = self.get_balance(&storage.borrow(), &to_address)?;
147 let b = NativeBalance(b) + NativeBalance(amount);
148 self.set_balance(storage, &to_address, b.into_vec())
149 }
150
151 fn burn(
152 &self,
153 storage: &mut BankStorageMut,
154 from_address: Addr,
155 amount: Vec<Coin>,
156 ) -> AnyResult<()> {
157 let amount = self.normalize_amount(amount)?;
158 let a = self.get_balance(&storage.borrow(), &from_address)?;
159 let a = (NativeBalance(a) - amount)?;
160 self.set_balance(storage, &from_address, a.into_vec())
161 }
162
163 fn normalize_amount(&self, amount: Vec<Coin>) -> AnyResult<Vec<Coin>> {
165 let res: Vec<_> = amount.into_iter().filter(|x| !x.amount.is_zero()).collect();
166 if res.is_empty() {
167 bail!("Cannot transfer empty coins amount")
168 } else {
169 Ok(res)
170 }
171 }
172}
173
174fn coins_to_string(coins: &[Coin]) -> String {
175 coins
176 .iter()
177 .map(|c| format!("{}{}", c.amount, c.denom))
178 .join(",")
179}
180
181impl Bank for BankKeeper {}
182
183impl Module for BankKeeper {
184 type ExecT = BankMsg;
185 type QueryT = BankQuery;
186 type SudoT = BankSudo;
187
188 fn execute<ExecC, QueryC>(
189 &self,
190 _api: &dyn Api,
191 storage: &mut dyn Storage,
192 _router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
193 _block: &BlockInfo,
194 sender: Addr,
195 msg: BankMsg,
196 ) -> AnyResult<AppResponse> {
197 let mut bank_storage_mut = BankStorageMut::new(storage);
198 match msg {
199 BankMsg::Send { to_address, amount } => {
200 let events = vec![Event::new("transfer")
202 .add_attribute("recipient", &to_address)
203 .add_attribute("sender", &sender)
204 .add_attribute("amount", coins_to_string(&amount))];
205 self.send(
206 &mut bank_storage_mut,
207 sender,
208 Addr::unchecked(to_address),
209 amount,
210 )?;
211 Ok(AppResponse {
212 events,
213 ..Default::default()
214 })
215 }
216 BankMsg::Burn { amount } => {
217 self.burn(&mut bank_storage_mut, sender, amount)?;
219 Ok(AppResponse::default())
220 }
221 other => unimplemented!("bank message: {other:?}"),
222 }
223 }
224
225 fn query(
226 &self,
227 api: &dyn Api,
228 storage: &dyn Storage,
229 _querier: &dyn Querier,
230 _block: &BlockInfo,
231 request: BankQuery,
232 ) -> AnyResult<Binary> {
233 let bank_storage = BankStorage::new(storage);
234 match request {
235 #[allow(deprecated)]
236 BankQuery::AllBalances { address } => {
237 let address = api.addr_validate(&address)?;
238 let amount = self.get_balance(&bank_storage, &address)?;
239 let res = AllBalanceResponse::new(amount);
240 to_json_binary(&res).map_err(Into::into)
241 }
242 BankQuery::Balance { address, denom } => {
243 let address = api.addr_validate(&address)?;
244 let all_amounts = self.get_balance(&bank_storage, &address)?;
245 let amount = all_amounts
246 .into_iter()
247 .find(|c| c.denom == denom)
248 .unwrap_or_else(|| coin(0, denom));
249 let res = BalanceResponse::new(amount);
250 to_json_binary(&res).map_err(Into::into)
251 }
252 #[cfg(feature = "cosmwasm_1_1")]
253 BankQuery::Supply { denom } => {
254 let amount = self.get_supply(&bank_storage, denom)?;
255 let res = SupplyResponse::new(amount);
256 to_json_binary(&res).map_err(Into::into)
257 }
258 #[cfg(feature = "cosmwasm_1_3")]
259 BankQuery::DenomMetadata { denom } => {
260 let meta = DENOM_METADATA.may_load(storage, denom)?.unwrap_or_default();
261 let res = DenomMetadataResponse::new(meta);
262 to_json_binary(&res).map_err(Into::into)
263 }
264 #[cfg(feature = "cosmwasm_1_3")]
265 BankQuery::AllDenomMetadata { pagination: _ } => {
266 let mut metadata = vec![];
267 for key in DENOM_METADATA.keys(storage, None, None, Order::Ascending) {
268 metadata.push(DENOM_METADATA.may_load(storage, key?)?.unwrap_or_default());
269 }
270 let res = AllDenomMetadataResponse::new(metadata, None);
271 to_json_binary(&res).map_err(Into::into)
272 }
273 other => unimplemented!("bank query: {:?}", other),
274 }
275 }
276
277 fn sudo<ExecC, QueryC>(
278 &self,
279 api: &dyn Api,
280 storage: &mut dyn Storage,
281 _router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
282 _block: &BlockInfo,
283 msg: BankSudo,
284 ) -> AnyResult<AppResponse> {
285 let mut bank_storage_mut = BankStorageMut::new(storage);
286 match msg {
287 BankSudo::Mint { to_address, amount } => {
288 let to_address = api.addr_validate(&to_address)?;
289 self.mint(&mut bank_storage_mut, to_address, amount)?;
290 Ok(AppResponse::default())
291 }
292 }
293 }
294}
295
296#[cfg(test)]
297mod test {
298 use super::*;
299
300 use crate::app::MockRouter;
301 use cosmwasm_std::testing::{mock_env, MockApi, MockQuerier, MockStorage};
302 use cosmwasm_std::{coins, from_json, Empty, StdError};
303
304 fn query_balance(
305 bank: &BankKeeper,
306 api: &dyn Api,
307 store: &dyn Storage,
308 rcpt: &Addr,
309 ) -> Vec<Coin> {
310 #[allow(deprecated)]
311 let req = BankQuery::AllBalances {
312 address: rcpt.clone().into(),
313 };
314 let block = mock_env().block;
315 let querier: MockQuerier<Empty> = MockQuerier::new(&[]);
316
317 let raw = bank.query(api, store, &querier, &block, req).unwrap();
318 let res: AllBalanceResponse = from_json(raw).unwrap();
319 res.amount
320 }
321
322 #[test]
323 #[cfg(feature = "cosmwasm_1_1")]
324 fn get_set_balance() {
325 let api = MockApi::default();
326 let mut store = MockStorage::new();
327 let block = mock_env().block;
328 let querier: MockQuerier<Empty> = MockQuerier::new(&[]);
329 let router = MockRouter::default();
330
331 let owner = api.addr_make("owner");
332 let rcpt = api.addr_make("receiver");
333 let init_funds = vec![coin(100, "eth"), coin(20, "btc")];
334 let norm = vec![coin(20, "btc"), coin(100, "eth")];
335
336 let bank = BankKeeper::new();
338 bank.init_balance(&mut store, &owner, init_funds).unwrap();
339
340 let bank_storage = BankStorage::new(&store);
341
342 let rich = bank.get_balance(&bank_storage, &owner).unwrap();
344 assert_eq!(rich, norm);
345 let poor = bank.get_balance(&bank_storage, &rcpt).unwrap();
346 assert_eq!(poor, vec![]);
347
348 #[allow(deprecated)]
350 let req = BankQuery::AllBalances {
351 address: owner.clone().into(),
352 };
353 let raw = bank.query(&api, &store, &querier, &block, req).unwrap();
354 let res: AllBalanceResponse = from_json(raw).unwrap();
355 assert_eq!(res.amount, norm);
356
357 #[allow(deprecated)]
358 let req = BankQuery::AllBalances {
359 address: rcpt.clone().into(),
360 };
361 let raw = bank.query(&api, &store, &querier, &block, req).unwrap();
362 let res: AllBalanceResponse = from_json(raw).unwrap();
363 assert_eq!(res.amount, vec![]);
364
365 let req = BankQuery::Balance {
366 address: owner.clone().into(),
367 denom: "eth".into(),
368 };
369 let raw = bank.query(&api, &store, &querier, &block, req).unwrap();
370 let res: BalanceResponse = from_json(raw).unwrap();
371 assert_eq!(res.amount, coin(100, "eth"));
372
373 let req = BankQuery::Balance {
374 address: owner.into(),
375 denom: "foobar".into(),
376 };
377 let raw = bank.query(&api, &store, &querier, &block, req).unwrap();
378 let res: BalanceResponse = from_json(raw).unwrap();
379 assert_eq!(res.amount, coin(0, "foobar"));
380
381 let req = BankQuery::Balance {
382 address: rcpt.clone().into(),
383 denom: "eth".into(),
384 };
385 let raw = bank.query(&api, &store, &querier, &block, req).unwrap();
386 let res: BalanceResponse = from_json(raw).unwrap();
387 assert_eq!(res.amount, coin(0, "eth"));
388
389 let req = BankQuery::Supply {
391 denom: "eth".into(),
392 };
393 let raw = bank.query(&api, &store, &querier, &block, req).unwrap();
394 let res: SupplyResponse = from_json(raw).unwrap();
395 assert_eq!(res.amount, coin(100, "eth"));
396
397 let msg = BankSudo::Mint {
399 to_address: rcpt.to_string(),
400 amount: norm.clone(),
401 };
402 bank.sudo(&api, &mut store, &router, &block, msg).unwrap();
403
404 #[allow(deprecated)]
406 let req = BankQuery::AllBalances {
407 address: rcpt.into(),
408 };
409 let raw = bank.query(&api, &store, &querier, &block, req).unwrap();
410 let res: AllBalanceResponse = from_json(raw).unwrap();
411 assert_eq!(res.amount, norm);
412
413 let req = BankQuery::Supply {
415 denom: "eth".into(),
416 };
417 let raw = bank.query(&api, &store, &querier, &block, req).unwrap();
418 let res: SupplyResponse = from_json(raw).unwrap();
419 assert_eq!(res.amount, coin(200, "eth"));
420 }
421
422 #[test]
423 fn send_coins() {
424 let api = MockApi::default();
425 let mut store = MockStorage::new();
426 let block = mock_env().block;
427 let router = MockRouter::default();
428
429 let owner = api.addr_make("owner");
430 let rcpt = api.addr_make("receiver");
431 let init_funds = vec![coin(20, "btc"), coin(100, "eth")];
432 let rcpt_funds = vec![coin(5, "btc")];
433
434 let bank = BankKeeper::new();
436 bank.init_balance(&mut store, &owner, init_funds).unwrap();
437 bank.init_balance(&mut store, &rcpt, rcpt_funds).unwrap();
438
439 let to_send = vec![coin(30, "eth"), coin(5, "btc")];
441 let msg = BankMsg::Send {
442 to_address: rcpt.clone().into(),
443 amount: to_send,
444 };
445 bank.execute(
446 &api,
447 &mut store,
448 &router,
449 &block,
450 owner.clone(),
451 msg.clone(),
452 )
453 .unwrap();
454 let rich = query_balance(&bank, &api, &store, &owner);
455 assert_eq!(vec![coin(15, "btc"), coin(70, "eth")], rich);
456 let poor = query_balance(&bank, &api, &store, &rcpt);
457 assert_eq!(vec![coin(10, "btc"), coin(30, "eth")], poor);
458
459 bank.execute(&api, &mut store, &router, &block, rcpt.clone(), msg)
461 .unwrap();
462
463 let msg = BankMsg::Send {
465 to_address: rcpt.into(),
466 amount: coins(20, "btc"),
467 };
468 bank.execute(&api, &mut store, &router, &block, owner.clone(), msg)
469 .unwrap_err();
470
471 let rich = query_balance(&bank, &api, &store, &owner);
472 assert_eq!(vec![coin(15, "btc"), coin(70, "eth")], rich);
473 }
474
475 #[test]
476 fn burn_coins() {
477 let api = MockApi::default();
478 let mut store = MockStorage::new();
479 let block = mock_env().block;
480 let router = MockRouter::default();
481
482 let owner = api.addr_make("owner");
483 let rcpt = api.addr_make("recipient");
484 let init_funds = vec![coin(20, "btc"), coin(100, "eth")];
485
486 let bank = BankKeeper::new();
488 bank.init_balance(&mut store, &owner, init_funds).unwrap();
489
490 let to_burn = vec![coin(30, "eth"), coin(5, "btc")];
492 let msg = BankMsg::Burn { amount: to_burn };
493 bank.execute(&api, &mut store, &router, &block, owner.clone(), msg)
494 .unwrap();
495 let rich = query_balance(&bank, &api, &store, &owner);
496 assert_eq!(vec![coin(15, "btc"), coin(70, "eth")], rich);
497
498 let msg = BankMsg::Burn {
500 amount: coins(20, "btc"),
501 };
502 let err = bank
503 .execute(&api, &mut store, &router, &block, owner.clone(), msg)
504 .unwrap_err();
505 assert!(matches!(err.downcast().unwrap(), StdError::Overflow { .. }));
506
507 let rich = query_balance(&bank, &api, &store, &owner);
508 assert_eq!(vec![coin(15, "btc"), coin(70, "eth")], rich);
509
510 let msg = BankMsg::Burn {
512 amount: coins(1, "btc"),
513 };
514 let err = bank
515 .execute(&api, &mut store, &router, &block, rcpt, msg)
516 .unwrap_err();
517 assert!(matches!(err.downcast().unwrap(), StdError::Overflow { .. }));
518 }
519
520 #[test]
521 #[cfg(feature = "cosmwasm_1_3")]
522 fn set_get_denom_metadata_should_work() {
523 let api = MockApi::default();
524 let mut store = MockStorage::new();
525 let block = mock_env().block;
526 let querier: MockQuerier<Empty> = MockQuerier::new(&[]);
527 let bank = BankKeeper::new();
528 let denom_eth_name = "eth".to_string();
530 bank.set_denom_metadata(
531 &mut store,
532 denom_eth_name.clone(),
533 DenomMetadata {
534 name: denom_eth_name.clone(),
535 ..Default::default()
536 },
537 )
538 .unwrap();
539 let req = BankQuery::DenomMetadata {
541 denom: denom_eth_name.clone(),
542 };
543 let raw = bank.query(&api, &store, &querier, &block, req).unwrap();
544 let res: DenomMetadataResponse = from_json(raw).unwrap();
545 assert_eq!(res.metadata.name, denom_eth_name);
546 }
547
548 #[test]
549 #[cfg(feature = "cosmwasm_1_3")]
550 fn set_get_all_denom_metadata_should_work() {
551 let api = MockApi::default();
552 let mut store = MockStorage::new();
553 let block = mock_env().block;
554 let querier: MockQuerier<Empty> = MockQuerier::new(&[]);
555 let bank = BankKeeper::new();
556 let denom_btc_name = "btc".to_string();
558 bank.set_denom_metadata(
559 &mut store,
560 denom_btc_name.clone(),
561 DenomMetadata {
562 name: denom_btc_name.clone(),
563 ..Default::default()
564 },
565 )
566 .unwrap();
567 let denom_eth_name = "eth".to_string();
569 bank.set_denom_metadata(
570 &mut store,
571 denom_eth_name.clone(),
572 DenomMetadata {
573 name: denom_eth_name.clone(),
574 ..Default::default()
575 },
576 )
577 .unwrap();
578 let req = BankQuery::AllDenomMetadata { pagination: None };
580 let raw = bank.query(&api, &store, &querier, &block, req).unwrap();
581 let res: AllDenomMetadataResponse = from_json(raw).unwrap();
582 assert_eq!(res.metadata[0].name, denom_btc_name);
583 assert_eq!(res.metadata[1].name, denom_eth_name);
584 }
585
586 #[test]
587 fn fail_on_zero_values() {
588 let api = MockApi::default();
589 let mut store = MockStorage::new();
590 let block = mock_env().block;
591 let router = MockRouter::default();
592
593 let owner = api.addr_make("owner");
594 let rcpt = api.addr_make("recipient");
595 let init_funds = vec![coin(5000, "atom"), coin(100, "eth")];
596
597 let bank = BankKeeper::new();
599 bank.init_balance(&mut store, &owner, init_funds).unwrap();
600
601 let msg = BankMsg::Send {
603 to_address: rcpt.to_string(),
604 amount: coins(100, "atom"),
605 };
606 bank.execute(&api, &mut store, &router, &block, owner.clone(), msg)
607 .unwrap();
608
609 let msg = BankMsg::Send {
611 to_address: rcpt.to_string(),
612 amount: vec![],
613 };
614 bank.execute(&api, &mut store, &router, &block, owner.clone(), msg)
615 .unwrap_err();
616
617 let msg = BankMsg::Send {
619 to_address: rcpt.to_string(),
620 amount: coins(0, "atom"),
621 };
622 bank.execute(&api, &mut store, &router, &block, owner.clone(), msg)
623 .unwrap_err();
624
625 let msg = BankMsg::Burn { amount: vec![] };
627 bank.execute(&api, &mut store, &router, &block, owner.clone(), msg)
628 .unwrap_err();
629
630 let msg = BankMsg::Burn {
632 amount: coins(0, "atom"),
633 };
634 bank.execute(&api, &mut store, &router, &block, owner, msg)
635 .unwrap_err();
636
637 let msg = BankSudo::Mint {
639 to_address: rcpt.to_string(),
640 amount: coins(4321, "atom"),
641 };
642 bank.sudo(&api, &mut store, &router, &block, msg).unwrap();
643
644 let msg = BankSudo::Mint {
646 to_address: rcpt.to_string(),
647 amount: coins(0, "atom"),
648 };
649 bank.sudo(&api, &mut store, &router, &block, msg)
650 .unwrap_err();
651
652 let msg = BankSudo::Mint {
654 to_address: rcpt.to_string(),
655 amount: vec![],
656 };
657 bank.sudo(&api, &mut store, &router, &block, msg)
658 .unwrap_err();
659 }
660}