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