1use crate::app::CosmosRouter;
2use crate::error::std_error_bail;
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, Api, BalanceResponse, BankMsg, BankQuery, Binary, BlockInfo, Coin,
10 DenomMetadata, Event, Querier, StdError, StdResult, Storage,
11};
12#[cfg(feature = "cosmwasm_1_3")]
13use cosmwasm_std::{AllDenomMetadataResponse, DenomMetadataResponse};
14#[cfg(feature = "cosmwasm_1_1")]
15use cosmwasm_std::{Order, SupplyResponse, Uint256};
16
17use cw_storage_plus::Map;
18use cw_utils::NativeBalance;
19use itertools::Itertools;
20use schemars::JsonSchema;
21
22const BALANCES: Map<&Addr, NativeBalance> = Map::new("balances");
24
25const DENOM_METADATA: Map<String, DenomMetadata> = Map::new("metadata");
27
28#[derive(Clone, Debug, PartialEq, Eq, JsonSchema)]
30pub enum BankSudo {
31 Mint {
33 to_address: String,
35 amount: Vec<Coin>,
37 },
38}
39
40pub trait Bank: Module<ExecT = BankMsg, QueryT = BankQuery, SudoT = BankSudo> {}
46
47#[derive(Default)]
53pub struct BankKeeper {}
54
55impl StoragePrefix for BankKeeper {
56 const NAMESPACE: &'static [u8] = b"bank";
57}
58type BankStorage<'a> = TypedPrefixedStorage<'a, BankKeeper>;
59type BankStorageMut<'a> = TypedPrefixedStorageMut<'a, BankKeeper>;
60
61impl BankKeeper {
62 pub fn new() -> Self {
64 Self::default()
65 }
66
67 pub fn init_balance(
69 &self,
70 storage: &mut dyn Storage,
71 account: &Addr,
72 amount: Vec<Coin>,
73 ) -> StdResult<()> {
74 let mut bank_storage = BankStorageMut::new(storage);
75 self.set_balance(&mut bank_storage, account, amount)
76 }
77
78 fn set_balance(
80 &self,
81 storage: &mut BankStorageMut,
82 account: &Addr,
83 amount: Vec<Coin>,
84 ) -> StdResult<()> {
85 let mut balance = NativeBalance(amount);
86 balance.normalize();
87 BALANCES.save(storage, account, &balance)
88 }
89
90 pub fn set_denom_metadata(
92 &self,
93 storage: &mut dyn Storage,
94 denom: String,
95 metadata: DenomMetadata,
96 ) -> StdResult<()> {
97 DENOM_METADATA.save(storage, denom, &metadata)
98 }
99
100 fn get_balance(&self, storage: &BankStorage, addr: &Addr) -> StdResult<Vec<Coin>> {
102 let val = BALANCES.may_load(storage, addr)?;
103 Ok(val.unwrap_or_default().into_vec())
104 }
105
106 #[cfg(feature = "cosmwasm_1_1")]
107 fn get_supply(&self, storage: &BankStorage, denom: String) -> StdResult<Coin> {
108 let supply: Uint256 = BALANCES
109 .range(storage, None, None, Order::Ascending)
110 .collect::<StdResult<Vec<_>>>()?
111 .into_iter()
112 .map(|a| a.1)
113 .fold(Uint256::zero(), |accum, item| {
114 let mut subtotal = Uint256::zero();
115 for coin in item.into_vec() {
116 if coin.denom == denom {
117 subtotal += coin.amount;
118 }
119 }
120 accum + subtotal
121 });
122 Ok(Coin::new(supply, denom))
123 }
124
125 fn send(
126 &self,
127 storage: &mut BankStorageMut,
128 from_address: Addr,
129 to_address: Addr,
130 amount: Vec<Coin>,
131 ) -> StdResult<()> {
132 self.burn(storage, from_address, amount.clone())?;
133 self.mint(storage, to_address, amount)
134 }
135
136 fn mint(
137 &self,
138 storage: &mut BankStorageMut,
139 to_address: Addr,
140 amount: Vec<Coin>,
141 ) -> StdResult<()> {
142 let amount = self.normalize_amount(amount)?;
143 let b = self.get_balance(&storage.borrow(), &to_address)?;
144 let b = NativeBalance(b) + NativeBalance(amount);
145 self.set_balance(storage, &to_address, b.into_vec())
146 }
147
148 fn burn(
149 &self,
150 storage: &mut BankStorageMut,
151 from_address: Addr,
152 amount: Vec<Coin>,
153 ) -> StdResult<()> {
154 let amount = self.normalize_amount(amount)?;
155 let a = self.get_balance(&storage.borrow(), &from_address)?;
156 let a = (NativeBalance(a) - amount)?;
157 self.set_balance(storage, &from_address, a.into_vec())
158 }
159
160 fn normalize_amount(&self, amount: Vec<Coin>) -> StdResult<Vec<Coin>> {
162 let res: Vec<_> = amount.into_iter().filter(|x| !x.amount.is_zero()).collect();
163 if res.is_empty() {
164 std_error_bail!("Cannot transfer empty coins amount")
165 } else {
166 Ok(res)
167 }
168 }
169}
170
171fn coins_to_string(coins: &[Coin]) -> String {
172 coins
173 .iter()
174 .map(|c| format!("{}{}", c.amount, c.denom))
175 .join(",")
176}
177
178impl Bank for BankKeeper {}
179
180impl Module for BankKeeper {
181 type ExecT = BankMsg;
182 type QueryT = BankQuery;
183 type SudoT = BankSudo;
184
185 fn execute<ExecC, QueryC>(
186 &self,
187 _api: &dyn Api,
188 storage: &mut dyn Storage,
189 _router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
190 _block: &BlockInfo,
191 sender: Addr,
192 msg: BankMsg,
193 ) -> StdResult<AppResponse> {
194 let mut bank_storage_mut = BankStorageMut::new(storage);
195 match msg {
196 BankMsg::Send { to_address, amount } => {
197 let events = vec![Event::new("transfer")
199 .add_attribute("recipient", &to_address)
200 .add_attribute("sender", &sender)
201 .add_attribute("amount", coins_to_string(&amount))];
202 self.send(
203 &mut bank_storage_mut,
204 sender,
205 Addr::unchecked(to_address),
206 amount,
207 )?;
208 Ok(AppResponse {
209 events,
210 ..Default::default()
211 })
212 }
213 BankMsg::Burn { amount } => {
214 self.burn(&mut bank_storage_mut, sender, amount)?;
216 Ok(AppResponse::default())
217 }
218 other => unimplemented!("bank message: {other:?}"),
219 }
220 }
221
222 fn query(
223 &self,
224 api: &dyn Api,
225 storage: &dyn Storage,
226 _querier: &dyn Querier,
227 _block: &BlockInfo,
228 request: BankQuery,
229 ) -> StdResult<Binary> {
230 let bank_storage = BankStorage::new(storage);
231 match request {
232 #[allow(deprecated)]
233 BankQuery::Balance { address, denom } => {
234 let address = api.addr_validate(&address)?;
235 let all_amounts = self.get_balance(&bank_storage, &address)?;
236 let amount = all_amounts
237 .into_iter()
238 .find(|c| c.denom == denom)
239 .unwrap_or_else(|| coin(0, denom));
240 let res = BalanceResponse::new(amount);
241 to_json_binary(&res)
242 }
243 #[cfg(feature = "cosmwasm_1_1")]
244 BankQuery::Supply { denom } => {
245 let amount = self.get_supply(&bank_storage, denom)?;
246 let res = SupplyResponse::new(amount);
247 to_json_binary(&res)
248 }
249 #[cfg(feature = "cosmwasm_1_3")]
250 BankQuery::DenomMetadata { denom } => {
251 let meta = DENOM_METADATA.may_load(storage, denom)?.unwrap_or_default();
252 let res = DenomMetadataResponse::new(meta);
253 to_json_binary(&res)
254 }
255 #[cfg(feature = "cosmwasm_1_3")]
256 BankQuery::AllDenomMetadata { pagination: _ } => {
257 let mut metadata = vec![];
258 for key in DENOM_METADATA.keys(storage, None, None, Order::Ascending) {
259 metadata.push(DENOM_METADATA.may_load(storage, key?)?.unwrap_or_default());
260 }
261 let res = AllDenomMetadataResponse::new(metadata, None);
262 to_json_binary(&res)
263 }
264 other => unimplemented!("bank query: {:?}", other),
265 }
266 }
267
268 fn sudo<ExecC, QueryC>(
269 &self,
270 api: &dyn Api,
271 storage: &mut dyn Storage,
272 _router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
273 _block: &BlockInfo,
274 msg: BankSudo,
275 ) -> StdResult<AppResponse> {
276 let mut bank_storage_mut = BankStorageMut::new(storage);
277 match msg {
278 BankSudo::Mint { to_address, amount } => {
279 let to_address = api.addr_validate(&to_address)?;
280 self.mint(&mut bank_storage_mut, to_address, amount)?;
281 Ok(AppResponse::default())
282 }
283 }
284 }
285}
286
287#[cfg(test)]
288mod test {
289 use super::*;
290
291 use crate::app::MockRouter;
292 use cosmwasm_std::testing::{mock_env, MockApi, MockQuerier, MockStorage};
293 use cosmwasm_std::{coins, from_json, Empty};
294
295 fn query_balance(
296 bank: &BankKeeper,
297 api: &dyn Api,
298 store: &dyn Storage,
299 address: &Addr,
300 denom: &str,
301 ) -> Coin {
302 let req = BankQuery::Balance {
303 address: address.into(),
304 denom: denom.to_string(),
305 };
306 let block = mock_env().block;
307 let querier: MockQuerier<Empty> = MockQuerier::new(&[]);
308 let raw = bank.query(api, store, &querier, &block, req).unwrap();
309 let res: BalanceResponse = from_json(raw).unwrap();
310 res.amount
311 }
312
313 #[test]
314 #[cfg(feature = "cosmwasm_1_1")]
315 fn get_set_balance() {
316 let api = MockApi::default();
317 let mut store = MockStorage::new();
318 let block = mock_env().block;
319 let querier: MockQuerier<Empty> = MockQuerier::new(&[]);
320 let _router = MockRouter::default();
321
322 let owner = api.addr_make("owner");
323 let rcpt = api.addr_make("receiver");
324 let init_funds = vec![coin(100, "eth"), coin(20, "btc")];
325 let norm = vec![coin(20, "btc"), coin(100, "eth")];
326
327 let bank = BankKeeper::new();
329 bank.init_balance(&mut store, &owner, init_funds).unwrap();
330
331 let bank_storage = BankStorage::new(&store);
332
333 let rich = bank.get_balance(&bank_storage, &owner).unwrap();
335 assert_eq!(rich, norm);
336 let poor = bank.get_balance(&bank_storage, &rcpt).unwrap();
337 assert_eq!(poor, vec![]);
338
339 let req = BankQuery::Balance {
341 address: owner.clone().into(),
342 denom: "btc".to_string(),
343 };
344 let raw = bank.query(&api, &store, &querier, &block, req).unwrap();
345 let res: BalanceResponse = from_json(raw).unwrap();
346 assert_eq!(norm[0], res.amount);
347
348 }
414
415 #[test]
416 fn send_coins() {
417 let api = MockApi::default();
418 let mut store = MockStorage::new();
419 let block = mock_env().block;
420 let router = MockRouter::default();
421
422 let owner = api.addr_make("owner");
423 let rcpt = api.addr_make("receiver");
424 let init_funds = vec![coin(20, "btc"), coin(100, "eth")];
425 let rcpt_funds = vec![coin(5, "btc")];
426
427 let bank = BankKeeper::new();
429 bank.init_balance(&mut store, &owner, init_funds).unwrap();
430 bank.init_balance(&mut store, &rcpt, rcpt_funds).unwrap();
431
432 let to_send = vec![coin(30, "eth"), coin(5, "btc")];
434 let msg = BankMsg::Send {
435 to_address: rcpt.clone().into(),
436 amount: to_send,
437 };
438 bank.execute(
439 &api,
440 &mut store,
441 &router,
442 &block,
443 owner.clone(),
444 msg.clone(),
445 )
446 .unwrap();
447 assert_eq!(
448 coin(15, "btc"),
449 query_balance(&bank, &api, &store, &owner, "btc")
450 );
451 assert_eq!(
452 coin(70, "eth"),
453 query_balance(&bank, &api, &store, &owner, "eth")
454 );
455 assert_eq!(
456 coin(10, "btc"),
457 query_balance(&bank, &api, &store, &rcpt, "btc")
458 );
459 assert_eq!(
460 coin(30, "eth"),
461 query_balance(&bank, &api, &store, &rcpt, "eth")
462 );
463
464 bank.execute(&api, &mut store, &router, &block, rcpt.clone(), msg)
466 .unwrap();
467
468 let msg = BankMsg::Send {
470 to_address: rcpt.into(),
471 amount: coins(20, "btc"),
472 };
473 bank.execute(&api, &mut store, &router, &block, owner.clone(), msg)
474 .unwrap_err();
475
476 assert_eq!(
477 coin(15, "btc"),
478 query_balance(&bank, &api, &store, &owner, "btc")
479 );
480 assert_eq!(
481 coin(70, "eth"),
482 query_balance(&bank, &api, &store, &owner, "eth")
483 );
484 }
485
486 #[test]
487 fn burn_coins() {
488 let api = MockApi::default();
489 let mut store = MockStorage::new();
490 let block = mock_env().block;
491 let router = MockRouter::default();
492
493 let owner = api.addr_make("owner");
494 let rcpt = api.addr_make("recipient");
495 let init_funds = vec![coin(20, "btc"), coin(100, "eth")];
496
497 let bank = BankKeeper::new();
499 bank.init_balance(&mut store, &owner, init_funds).unwrap();
500
501 let to_burn = vec![coin(30, "eth"), coin(5, "btc")];
503 let msg = BankMsg::Burn { amount: to_burn };
504 bank.execute(&api, &mut store, &router, &block, owner.clone(), msg)
505 .unwrap();
506 assert_eq!(
507 coin(15, "btc"),
508 query_balance(&bank, &api, &store, &owner, "btc")
509 );
510 assert_eq!(
511 coin(70, "eth"),
512 query_balance(&bank, &api, &store, &owner, "eth")
513 );
514
515 let msg = BankMsg::Burn {
517 amount: coins(20, "btc"),
518 };
519 let err = bank
520 .execute(&api, &mut store, &router, &block, owner.clone(), msg)
521 .unwrap_err();
522 assert_eq!(
523 "kind: Overflow, error: Cannot Sub with given operands",
524 err.to_string()
525 );
526
527 assert_eq!(
528 coin(15, "btc"),
529 query_balance(&bank, &api, &store, &owner, "btc")
530 );
531 assert_eq!(
532 coin(70, "eth"),
533 query_balance(&bank, &api, &store, &owner, "eth")
534 );
535
536 let msg = BankMsg::Burn {
538 amount: coins(1, "btc"),
539 };
540 let err = bank
541 .execute(&api, &mut store, &router, &block, rcpt, msg)
542 .unwrap_err();
543 assert_eq!(
544 "kind: Overflow, error: Cannot Sub with given operands",
545 err.to_string()
546 );
547 }
548
549 #[test]
550 #[cfg(feature = "cosmwasm_1_3")]
551 fn set_get_denom_metadata_should_work() {
552 let api = MockApi::default();
553 let mut store = MockStorage::new();
554 let block = mock_env().block;
555 let querier: MockQuerier<Empty> = MockQuerier::new(&[]);
556 let bank = BankKeeper::new();
557 let denom_eth_name = "eth".to_string();
559 bank.set_denom_metadata(
560 &mut store,
561 denom_eth_name.clone(),
562 DenomMetadata {
563 name: denom_eth_name.clone(),
564 ..Default::default()
565 },
566 )
567 .unwrap();
568 let req = BankQuery::DenomMetadata {
570 denom: denom_eth_name.clone(),
571 };
572 let raw = bank.query(&api, &store, &querier, &block, req).unwrap();
573 let res: DenomMetadataResponse = from_json(raw).unwrap();
574 assert_eq!(res.metadata.name, denom_eth_name);
575 }
576
577 #[test]
578 #[cfg(feature = "cosmwasm_1_3")]
579 fn set_get_all_denom_metadata_should_work() {
580 let api = MockApi::default();
581 let mut store = MockStorage::new();
582 let block = mock_env().block;
583 let querier: MockQuerier<Empty> = MockQuerier::new(&[]);
584 let bank = BankKeeper::new();
585 let denom_btc_name = "btc".to_string();
587 bank.set_denom_metadata(
588 &mut store,
589 denom_btc_name.clone(),
590 DenomMetadata {
591 name: denom_btc_name.clone(),
592 ..Default::default()
593 },
594 )
595 .unwrap();
596 let denom_eth_name = "eth".to_string();
598 bank.set_denom_metadata(
599 &mut store,
600 denom_eth_name.clone(),
601 DenomMetadata {
602 name: denom_eth_name.clone(),
603 ..Default::default()
604 },
605 )
606 .unwrap();
607 let req = BankQuery::AllDenomMetadata { pagination: None };
609 let raw = bank.query(&api, &store, &querier, &block, req).unwrap();
610 let res: AllDenomMetadataResponse = from_json(raw).unwrap();
611 assert_eq!(res.metadata[0].name, denom_btc_name);
612 assert_eq!(res.metadata[1].name, denom_eth_name);
613 }
614
615 #[test]
616 fn fail_on_zero_values() {
617 let api = MockApi::default();
618 let mut store = MockStorage::new();
619 let block = mock_env().block;
620 let router = MockRouter::default();
621
622 let owner = api.addr_make("owner");
623 let rcpt = api.addr_make("recipient");
624 let init_funds = vec![coin(5000, "atom"), coin(100, "eth")];
625
626 let bank = BankKeeper::new();
628 bank.init_balance(&mut store, &owner, init_funds).unwrap();
629
630 let msg = BankMsg::Send {
632 to_address: rcpt.to_string(),
633 amount: coins(100, "atom"),
634 };
635 bank.execute(&api, &mut store, &router, &block, owner.clone(), msg)
636 .unwrap();
637
638 let msg = BankMsg::Send {
640 to_address: rcpt.to_string(),
641 amount: vec![],
642 };
643 bank.execute(&api, &mut store, &router, &block, owner.clone(), msg)
644 .unwrap_err();
645
646 let msg = BankMsg::Send {
648 to_address: rcpt.to_string(),
649 amount: coins(0, "atom"),
650 };
651 bank.execute(&api, &mut store, &router, &block, owner.clone(), msg)
652 .unwrap_err();
653
654 let msg = BankMsg::Burn { amount: vec![] };
656 bank.execute(&api, &mut store, &router, &block, owner.clone(), msg)
657 .unwrap_err();
658
659 let msg = BankMsg::Burn {
661 amount: coins(0, "atom"),
662 };
663 bank.execute(&api, &mut store, &router, &block, owner, msg)
664 .unwrap_err();
665
666 let msg = BankSudo::Mint {
668 to_address: rcpt.to_string(),
669 amount: coins(4321, "atom"),
670 };
671 bank.sudo(&api, &mut store, &router, &block, msg).unwrap();
672
673 let msg = BankSudo::Mint {
675 to_address: rcpt.to_string(),
676 amount: coins(0, "atom"),
677 };
678 bank.sudo(&api, &mut store, &router, &block, msg)
679 .unwrap_err();
680
681 let msg = BankSudo::Mint {
683 to_address: rcpt.to_string(),
684 amount: vec![],
685 };
686 bank.sudo(&api, &mut store, &router, &block, msg)
687 .unwrap_err();
688 }
689}