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}