kujira_rs_testing/
mock.rs

1use std::{collections::HashMap, convert::TryInto};
2
3use anyhow::{Error, Result as AnyResult};
4use cosmwasm_std::{
5    attr, testing::MockStorage, to_json_binary, Addr, BankMsg, Coin, CosmosMsg, Decimal, Empty,
6    Event, Uint128,
7};
8
9use cw_multi_test::{
10    App, AppResponse, BankKeeper, BankSudo, BasicAppBuilder, CosmosRouter, DistributionKeeper,
11    Module, StakeKeeper, SudoMsg, WasmKeeper,
12};
13
14use cw_storage_plus::Map;
15use kujira::{
16    BankQuery, DenomAdminResponse, DenomMsg, DenomQuery, ExchangeRateResponse, FullDenomResponse,
17    KujiraMsg, KujiraQuery, OracleQuery, SupplyResponse,
18};
19
20use crate::{address::MockAddressGenerator, api::MockApiBech32};
21
22pub type CustomApp = App<
23    BankKeeper,
24    MockApiBech32,
25    MockStorage,
26    KujiraModule,
27    WasmKeeper<KujiraMsg, KujiraQuery>,
28    StakeKeeper,
29    DistributionKeeper,
30>;
31
32pub fn mock_app(balances: Vec<(Addr, Vec<Coin>)>) -> CustomApp {
33    let mut custom = KujiraModule {
34        oracle_prices: HashMap::new(),
35    };
36    custom.set_oracle_price(Decimal::from_ratio(1425u128, 100u128), "factory/owner/coll");
37    custom.set_oracle_price(Decimal::one(), "factory/contract0/uusk");
38
39    BasicAppBuilder::new_custom()
40        .with_custom(custom)
41        .with_api(MockApiBech32::new("kujira"))
42        .with_wasm(WasmKeeper::default().with_address_generator(MockAddressGenerator))
43        .build(|router, _, storage| {
44            for (addr, coins) in balances {
45                router.bank.init_balance(storage, &addr, coins).unwrap();
46            }
47        })
48}
49
50pub struct KujiraModule {
51    pub oracle_prices: HashMap<String, Decimal>,
52}
53
54impl KujiraModule {
55    pub fn set_oracle_price(&mut self, price: Decimal, denom: &str) {
56        self.oracle_prices.insert(denom.to_string(), price);
57    }
58
59    fn subdenom_to_full(sender: impl Into<String>, subdenom: impl Into<String>) -> String {
60        format!("factory/{}/{}", sender.into(), subdenom.into())
61    }
62}
63
64static DENOM_ADMINS: Map<String, Addr> = Map::new("denom_admins");
65
66impl Module for KujiraModule {
67    type ExecT = KujiraMsg;
68
69    type QueryT = KujiraQuery;
70
71    type SudoT = Empty;
72
73    fn execute<ExecC, QueryC>(
74        &self,
75        api: &dyn cosmwasm_std::Api,
76        storage: &mut dyn cosmwasm_std::Storage,
77        router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
78        block: &cosmwasm_std::BlockInfo,
79        sender: Addr,
80        msg: Self::ExecT,
81    ) -> AnyResult<AppResponse>
82    where
83        ExecC: std::fmt::Debug
84            + Clone
85            + PartialEq
86            + schemars::JsonSchema
87            + serde::de::DeserializeOwned
88            + cosmwasm_std::CustomMsg
89            + 'static,
90        QueryC: cosmwasm_std::CustomQuery + serde::de::DeserializeOwned + 'static,
91    {
92        match msg {
93            KujiraMsg::Auth(_) | KujiraMsg::Batch(_) | KujiraMsg::Ica(_) => todo!(),
94            KujiraMsg::Denom(d) => match d {
95                DenomMsg::Create { subdenom } => {
96                    let full = Self::subdenom_to_full(sender.clone(), subdenom.to_string());
97                    storage.set(full.as_bytes(), &Uint128::zero().to_be_bytes());
98                    DENOM_ADMINS.save(storage, full, &sender)?;
99
100                    Ok(AppResponse {
101                        events: vec![],
102                        data: None,
103                    })
104                }
105                DenomMsg::Mint {
106                    amount,
107                    denom,
108                    recipient,
109                } => {
110                    let admin = DENOM_ADMINS.load(storage, denom.to_string())?;
111                    if admin != sender {
112                        return Err(Error::msg("Unauthorized"));
113                    }
114
115                    let mut supply = storage
116                        .get(denom.as_bytes())
117                        .map(|bz| u128::from_be_bytes(bz.try_into().unwrap()))
118                        .map(Uint128::from)
119                        .unwrap_or_default();
120
121                    supply += amount;
122                    storage.set(denom.as_bytes(), &supply.to_be_bytes());
123                    router.sudo(
124                        api,
125                        storage,
126                        block,
127                        SudoMsg::Bank(BankSudo::Mint {
128                            to_address: recipient.to_string(),
129                            amount: denom.coins(&amount),
130                        }),
131                    )?;
132                    Ok(AppResponse {
133                        events: vec![Event::new("mint").add_attributes(vec![
134                            attr("amount", amount),
135                            attr("denom", denom.to_string()),
136                            attr("recipient", recipient),
137                        ])],
138                        data: None,
139                    })
140                }
141                DenomMsg::Burn { denom, amount } => {
142                    let mut supply = storage
143                        .get(denom.as_bytes())
144                        .map(|bz| u128::from_be_bytes(bz.try_into().unwrap()))
145                        .map(Uint128::from)
146                        .unwrap_or_default();
147                    supply -= amount;
148                    storage.set(denom.as_bytes(), &supply.to_be_bytes());
149
150                    router.execute(
151                        api,
152                        storage,
153                        block,
154                        sender,
155                        CosmosMsg::Bank(BankMsg::Burn {
156                            amount: denom.coins(&amount),
157                        }),
158                    )?;
159
160                    Ok(AppResponse {
161                        events: vec![Event::new("burn").add_attributes(vec![
162                            attr("amount", amount),
163                            attr("denom", denom.to_string()),
164                        ])],
165                        data: None,
166                    })
167                }
168                DenomMsg::ChangeAdmin { denom, address } => {
169                    let admin = DENOM_ADMINS.load(storage, denom.to_string())?;
170                    if admin != sender {
171                        return Err(Error::msg("Unauthorized"));
172                    }
173                    DENOM_ADMINS.save(storage, denom.to_string(), &address)?;
174
175                    Ok(AppResponse {
176                        events: vec![],
177                        data: None,
178                    })
179                }
180            },
181        }
182    }
183
184    fn sudo<ExecC, QueryC>(
185        &self,
186        _api: &dyn cosmwasm_std::Api,
187        _storage: &mut dyn cosmwasm_std::Storage,
188        _router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
189        _block: &cosmwasm_std::BlockInfo,
190        _msg: Self::SudoT,
191    ) -> AnyResult<AppResponse>
192    where
193        ExecC: std::fmt::Debug
194            + Clone
195            + PartialEq
196            + schemars::JsonSchema
197            + serde::de::DeserializeOwned
198            + 'static,
199        QueryC: cosmwasm_std::CustomQuery + serde::de::DeserializeOwned + 'static,
200    {
201        todo!()
202    }
203
204    fn query(
205        &self,
206        _api: &dyn cosmwasm_std::Api,
207        storage: &dyn cosmwasm_std::Storage,
208        _querier: &dyn cosmwasm_std::Querier,
209        _block: &cosmwasm_std::BlockInfo,
210        request: Self::QueryT,
211    ) -> AnyResult<cosmwasm_std::Binary> {
212        match request {
213            KujiraQuery::Bank(b) => match b {
214                BankQuery::Supply { denom } => {
215                    let supply = storage
216                        .get(denom.as_bytes())
217                        .map(|bz| u128::from_be_bytes(bz.try_into().unwrap()))
218                        .unwrap_or_default();
219
220                    Ok(to_json_binary(&SupplyResponse {
221                        amount: denom.coin(&Uint128::from(supply)),
222                    })?)
223                }
224            },
225            KujiraQuery::Oracle(o) => match o {
226                OracleQuery::ExchangeRate { denom } => Ok(to_json_binary(&ExchangeRateResponse {
227                    rate: *self.oracle_prices.get(&denom).unwrap_or(&Decimal::zero()),
228                })?),
229            },
230            KujiraQuery::Denom(msg) => match msg {
231                DenomQuery::FullDenom {
232                    creator_addr,
233                    subdenom,
234                } => Ok(to_json_binary(&FullDenomResponse {
235                    denom: format!("factory/{}/{}", creator_addr, subdenom).into(),
236                })?),
237                DenomQuery::DenomAdmin { subdenom } => Ok(to_json_binary(&DenomAdminResponse {
238                    admin: DENOM_ADMINS.load(storage, subdenom.to_string())?,
239                })?),
240            },
241            KujiraQuery::Ica(_) => todo!("ICA queries not implemented in mock"),
242            KujiraQuery::Ibc(_) => todo!("IBC-Verify queries not implemented in mock"),
243        }
244    }
245}