clone_cw_multi_test/
bank.rs

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 crate::queries::bank::BankRemoteQuerier;
7use crate::wasm_emulation::channel::RemoteChannel;
8use crate::wasm_emulation::input::BankStorage;
9use crate::wasm_emulation::query::AllBankQuerier;
10use cosmwasm_std::{
11    coin, to_json_binary, Addr, AllBalanceResponse, Api, BalanceResponse, BankMsg, BankQuery,
12    Binary, BlockInfo, Coin, Event, Order, Querier, Storage,
13};
14use cosmwasm_std::{StdResult, SupplyResponse, Uint128};
15use cw_storage_plus::Map;
16use cw_utils::NativeBalance;
17use itertools::Itertools;
18use schemars::JsonSchema;
19
20pub(crate) const BALANCES: Map<&Addr, NativeBalance> = Map::new("balances");
21
22pub const NAMESPACE_BANK: &[u8] = b"bank";
23
24#[derive(Clone, std::fmt::Debug, PartialEq, Eq, JsonSchema)]
25pub enum BankSudo {
26    Mint {
27        to_address: String,
28        amount: Vec<Coin>,
29    },
30}
31
32pub trait Bank:
33    Module<ExecT = BankMsg, QueryT = BankQuery, SudoT = BankSudo> + AllBankQuerier
34{
35}
36
37#[derive(Default)]
38pub struct BankKeeper {
39    remote: Option<RemoteChannel>,
40}
41
42impl BankKeeper {
43    pub fn new() -> Self {
44        BankKeeper::default()
45    }
46
47    pub fn with_remote(mut self, remote: RemoteChannel) -> Self {
48        self.remote = Some(remote);
49        self
50    }
51
52    // this is an "admin" function to let us adjust bank accounts in genesis
53    pub fn init_balance(
54        &self,
55        storage: &mut dyn Storage,
56        account: &Addr,
57        amount: Vec<Coin>,
58    ) -> AnyResult<()> {
59        let mut bank_storage = prefixed(storage, NAMESPACE_BANK);
60        self.set_balance(&mut bank_storage, account, amount)
61    }
62
63    // this is an "admin" function to let us adjust bank accounts
64    fn set_balance(
65        &self,
66        bank_storage: &mut dyn Storage,
67        account: &Addr,
68        amount: Vec<Coin>,
69    ) -> AnyResult<()> {
70        let mut balance = NativeBalance(amount);
71        balance.normalize();
72        BALANCES
73            .save(bank_storage, account, &balance)
74            .map_err(Into::into)
75    }
76
77    fn get_balance(&self, bank_storage: &dyn Storage, account: &Addr) -> AnyResult<Vec<Coin>> {
78        // If there is no balance present, we query it on the distant chain
79        if let Some(val) = BALANCES.may_load(bank_storage, account)? {
80            Ok(val.into_vec())
81        } else {
82            BankRemoteQuerier::get_balance(self.remote.clone().unwrap(), account)
83        }
84    }
85
86    fn get_supply(&self, bank_storage: &dyn Storage, denom: String) -> AnyResult<Coin> {
87        let supply: Uint128 = BALANCES
88            .range(bank_storage, None, None, Order::Ascending)
89            .collect::<StdResult<Vec<_>>>()?
90            .into_iter()
91            .map(|a| a.1)
92            .fold(Uint128::zero(), |accum, item| {
93                let mut subtotal = Uint128::zero();
94                for coin in item.into_vec() {
95                    if coin.denom == denom {
96                        subtotal += coin.amount;
97                    }
98                }
99                accum + subtotal
100            });
101        Ok(coin(supply.into(), denom))
102    }
103
104    fn send(
105        &self,
106        bank_storage: &mut dyn Storage,
107        from_address: Addr,
108        to_address: Addr,
109        amount: Vec<Coin>,
110    ) -> AnyResult<()> {
111        self.burn(bank_storage, from_address, amount.clone())?;
112        self.mint(bank_storage, to_address, amount)
113    }
114
115    fn mint(
116        &self,
117        bank_storage: &mut dyn Storage,
118        to_address: Addr,
119        amount: Vec<Coin>,
120    ) -> AnyResult<()> {
121        let amount = self.normalize_amount(amount)?;
122        let b = self.get_balance(bank_storage, &to_address)?;
123        let b = NativeBalance(b) + NativeBalance(amount);
124        self.set_balance(bank_storage, &to_address, b.into_vec())
125    }
126
127    fn burn(
128        &self,
129        bank_storage: &mut dyn Storage,
130        from_address: Addr,
131        amount: Vec<Coin>,
132    ) -> AnyResult<()> {
133        let amount = self.normalize_amount(amount)?;
134        let a = self.get_balance(bank_storage, &from_address)?;
135        let a = (NativeBalance(a) - amount)?;
136        self.set_balance(bank_storage, &from_address, a.into_vec())
137    }
138
139    /// Filters out all 0 value coins and returns an error if the resulting Vec is empty
140    fn normalize_amount(&self, amount: Vec<Coin>) -> AnyResult<Vec<Coin>> {
141        let res: Vec<_> = amount.into_iter().filter(|x| !x.amount.is_zero()).collect();
142        if res.is_empty() {
143            bail!("Cannot transfer empty coins amount")
144        } else {
145            Ok(res)
146        }
147    }
148}
149
150fn coins_to_string(coins: &[Coin]) -> String {
151    coins
152        .iter()
153        .map(|c| format!("{}{}", c.amount, c.denom))
154        .join(",")
155}
156
157impl Bank for BankKeeper {}
158
159impl Module for BankKeeper {
160    type ExecT = BankMsg;
161    type QueryT = BankQuery;
162    type SudoT = BankSudo;
163
164    fn execute<ExecC, QueryC>(
165        &self,
166        _api: &dyn Api,
167        storage: &mut dyn Storage,
168        _router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
169        _block: &BlockInfo,
170        sender: Addr,
171        msg: BankMsg,
172    ) -> AnyResult<AppResponse> {
173        let mut bank_storage = prefixed(storage, NAMESPACE_BANK);
174        match msg {
175            BankMsg::Send { to_address, amount } => {
176                // see https://github.com/cosmos/cosmos-sdk/blob/v0.42.7/x/bank/keeper/send.go#L142-L147
177                let events = vec![Event::new("transfer")
178                    .add_attribute("recipient", &to_address)
179                    .add_attribute("sender", &sender)
180                    .add_attribute("amount", coins_to_string(&amount))];
181                self.send(
182                    &mut bank_storage,
183                    sender,
184                    Addr::unchecked(to_address),
185                    amount,
186                )?;
187                Ok(AppResponse { events, data: None })
188            }
189            BankMsg::Burn { amount } => {
190                // burn doesn't seem to emit any events
191                self.burn(&mut bank_storage, sender, amount)?;
192                Ok(AppResponse::default())
193            }
194            m => bail!("Unsupported bank message: {:?}", m),
195        }
196    }
197
198    fn sudo<ExecC, QueryC>(
199        &self,
200        api: &dyn Api,
201        storage: &mut dyn Storage,
202        _router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
203        _block: &BlockInfo,
204        msg: BankSudo,
205    ) -> AnyResult<AppResponse> {
206        let mut bank_storage = prefixed(storage, NAMESPACE_BANK);
207        match msg {
208            BankSudo::Mint { to_address, amount } => {
209                let to_address = api.addr_validate(&to_address)?;
210                self.mint(&mut bank_storage, to_address, amount)?;
211                Ok(AppResponse::default())
212            }
213        }
214    }
215
216    fn query(
217        &self,
218        api: &dyn Api,
219        storage: &dyn Storage,
220        _querier: &dyn Querier,
221        _block: &BlockInfo,
222        request: BankQuery,
223    ) -> AnyResult<Binary> {
224        let bank_storage = prefixed_read(storage, NAMESPACE_BANK);
225        match request {
226            BankQuery::AllBalances { address } => {
227                let address = api.addr_validate(&address)?;
228                let amount = self.get_balance(&bank_storage, &address)?;
229                let res = AllBalanceResponse::new(amount);
230                Ok(to_json_binary(&res)?)
231            }
232            BankQuery::Balance { address, denom } => {
233                let address = api.addr_validate(&address)?;
234                let all_amounts = self.get_balance(&bank_storage, &address)?;
235                let amount = all_amounts
236                    .into_iter()
237                    .find(|c| c.denom == denom)
238                    .unwrap_or_else(|| coin(0, denom));
239                let res = BalanceResponse::new(amount);
240                Ok(to_json_binary(&res)?)
241            }
242            BankQuery::Supply { denom } => {
243                let amount = self.get_supply(&bank_storage, denom)?;
244                let res = SupplyResponse::new(amount);
245                Ok(to_json_binary(&res)?)
246            }
247            q => bail!("Unsupported bank query: {:?}", q),
248        }
249    }
250}
251
252impl AllBankQuerier for BankKeeper {
253    fn query_all(&self, storage: &dyn Storage) -> AnyResult<BankStorage> {
254        let bank_storage = prefixed_read(storage, NAMESPACE_BANK);
255        let balances: Result<Vec<_>, _> = BALANCES
256            .range(&bank_storage, None, None, Order::Ascending)
257            .collect();
258        Ok(BankStorage { storage: balances? })
259    }
260}