1#[cfg(not(feature = "library"))]
2use cosmwasm_std::entry_point;
3use cosmwasm_std::{
4 from_binary, to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Order, Reply, ReplyOn,
5 Response, StdError, StdResult, SubMsg, WasmMsg,
6};
7use cw2::{get_contract_version, set_contract_version, ContractVersion};
8use cw_storage_plus::Bound;
9use cw_utils::parse_reply_instantiate_data;
10
11use komple_framework_types::shared::execute::SharedExecuteMsg;
12use komple_framework_types::shared::query::ResponseWrapper;
13use komple_framework_types::shared::RegisterMsg;
14use komple_framework_utils::check_admin_privileges;
15use komple_framework_utils::response::{EventHelper, ResponseHelper};
16use komple_framework_utils::shared::execute_update_operators;
17use semver::Version;
18
19use crate::error::ContractError;
20use crate::msg::{
21 ConfigResponse, ExecuteMsg, InstantiateMsg, MigrateMsg, ModulesResponse, QueryMsg,
22};
23use crate::state::{
24 Config, HubInfo, CONFIG, HUB_INFO, MARBU_FEE_MODULE, MODULES, MODULE_ID, MODULE_TO_REGISTER,
25 OPERATORS,
26};
27
28const CONTRACT_NAME: &str = "crates.io:komple-framework-hub-module";
30const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
31
32const MAX_DESCRIPTION_LENGTH: u32 = 512;
33
34#[cfg_attr(not(feature = "library"), entry_point)]
35pub fn instantiate(
36 deps: DepsMut,
37 _env: Env,
38 info: MessageInfo,
39 msg: RegisterMsg,
40) -> Result<Response, ContractError> {
41 set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
42
43 if msg.data.is_none() {
45 return Err(ContractError::InvalidInstantiateMsg {});
46 };
47 let data: InstantiateMsg = from_binary(&msg.data.unwrap())?;
48
49 let admin = deps.api.addr_validate(&msg.admin)?;
50 let config = Config { admin: admin.clone() };
51 CONFIG.save(deps.storage, &config)?;
52
53 OPERATORS.save(deps.storage, &vec![info.sender])?;
54
55 if data.hub_info.description.len() > MAX_DESCRIPTION_LENGTH as usize {
56 return Err(ContractError::DescriptionTooLong {});
57 }
58
59 if let Some(marbu_fee_module) = data.marbu_fee_module {
62 let marbu_fee_module = deps.api.addr_validate(&marbu_fee_module)?;
63 MARBU_FEE_MODULE.save(deps.storage, &marbu_fee_module)?;
64 }
65
66 HUB_INFO.save(deps.storage, &data.hub_info)?;
67
68 MODULE_ID.save(deps.storage, &0)?;
69
70 Ok(ResponseHelper::new_module("hub", "instantiate").add_event(
71 EventHelper::new("hub_instantiate")
72 .add_attribute("admin", config.admin)
73 .get(),
74 ))
75}
76
77#[cfg_attr(not(feature = "library"), entry_point)]
78pub fn execute(
79 deps: DepsMut,
80 env: Env,
81 info: MessageInfo,
82 msg: ExecuteMsg,
83) -> Result<Response, ContractError> {
84 match msg {
85 ExecuteMsg::RegisterModule {
86 code_id,
87 module,
88 msg,
89 } => execute_register_module(deps, env, info, code_id, module, msg),
90 ExecuteMsg::UpdateHubInfo {
91 name,
92 description,
93 image,
94 external_link,
95 } => execute_update_hub_info(deps, env, info, name, description, image, external_link),
96 ExecuteMsg::DeregisterModule { module } => {
97 execute_deregister_module(deps, env, info, module)
98 }
99 ExecuteMsg::UpdateOperators { addrs } => {
100 let config = CONFIG.load(deps.storage)?;
101 let res = execute_update_operators(
102 deps,
103 info,
104 "hub",
105 &env.contract.address,
106 &config.admin,
107 OPERATORS,
108 addrs,
109 );
110 match res {
111 Ok(res) => Ok(res),
112 Err(err) => Err(err.into()),
113 }
114 }
115 ExecuteMsg::MigrateContracts {
116 code_id,
117 contract_address,
118 msg,
119 } => execute_migrate_contracts(deps, env, info, code_id, contract_address, msg),
120 }
121}
122
123fn execute_register_module(
124 deps: DepsMut,
125 env: Env,
126 info: MessageInfo,
127 code_id: u64,
128 module: String,
129 msg: Option<Binary>,
130) -> Result<Response, ContractError> {
131 let operators = OPERATORS.may_load(deps.storage)?;
132 let config = CONFIG.load(deps.storage)?;
133 check_admin_privileges(
134 &info.sender,
135 &env.contract.address,
136 &config.admin,
137 None,
138 operators,
139 )?;
140
141 let module_id = (MODULE_ID.load(deps.storage)?) + 1;
143
144 let register_msg = RegisterMsg {
148 admin: config.admin.to_string(),
149 data: msg,
150 };
151
152 let sub_msg: SubMsg = SubMsg {
153 msg: WasmMsg::Instantiate {
154 code_id,
155 msg: to_binary(®ister_msg)?,
156 funds: vec![],
157 admin: Some(env.contract.address.to_string()),
158 label: format!("Komple Framework Module - {}", module.as_str()),
159 }
160 .into(),
161 id: module_id,
162 gas_limit: None,
163 reply_on: ReplyOn::Success,
164 };
165
166 MODULE_ID.save(deps.storage, &module_id)?;
167 MODULE_TO_REGISTER.save(deps.storage, &module)?;
170
171 Ok(ResponseHelper::new_module("hub", "register_module")
172 .add_submessage(sub_msg)
173 .add_event(
174 EventHelper::new("hub_register_module")
175 .add_attribute("module", module)
176 .get(),
177 ))
178}
179
180fn execute_update_hub_info(
181 deps: DepsMut,
182 env: Env,
183 info: MessageInfo,
184 name: String,
185 description: String,
186 image: String,
187 external_link: Option<String>,
188) -> Result<Response, ContractError> {
189 let operators = OPERATORS.may_load(deps.storage)?;
190 let config = CONFIG.load(deps.storage)?;
191
192 check_admin_privileges(
193 &info.sender,
194 &env.contract.address,
195 &config.admin,
196 None,
197 operators,
198 )?;
199
200 let hub_info = HubInfo {
201 name,
202 description,
203 image,
204 external_link,
205 };
206 HUB_INFO.save(deps.storage, &hub_info)?;
207
208 Ok(
209 ResponseHelper::new_module("hub", "update_hub_info").add_event(
210 EventHelper::new("hub_update_hub_info")
211 .add_attribute("name", hub_info.name)
212 .add_attribute("description", hub_info.description)
213 .add_attribute("image", hub_info.image)
214 .check_add_attribute(
215 &hub_info.external_link,
216 "external_link",
217 hub_info.external_link.as_ref().unwrap_or(&String::from("")),
218 )
219 .get(),
220 ),
221 )
222}
223
224fn execute_deregister_module(
225 deps: DepsMut,
226 env: Env,
227 info: MessageInfo,
228 module: String,
229) -> Result<Response, ContractError> {
230 let operators = OPERATORS.may_load(deps.storage)?;
231 let config = CONFIG.load(deps.storage)?;
232 check_admin_privileges(
233 &info.sender,
234 &env.contract.address,
235 &config.admin,
236 None,
237 operators,
238 )?;
239
240 let module_addr = MODULES.load(deps.storage, module.clone());
241 if module_addr.is_err() {
242 return Err(ContractError::InvalidModule {});
243 }
244
245 let mut msgs: Vec<WasmMsg> = vec![];
246
247 msgs.push(WasmMsg::Execute {
249 contract_addr: module_addr.as_ref().unwrap().to_string(),
250 msg: to_binary(&SharedExecuteMsg::LockExecute {})?,
251 funds: vec![],
252 });
253
254 msgs.push(WasmMsg::ClearAdmin {
256 contract_addr: module_addr.unwrap().to_string(),
257 });
258
259 MODULES.remove(deps.storage, module.clone());
260
261 Ok(ResponseHelper::new_module("hub", "deregister_module")
262 .add_messages(msgs)
263 .add_event(
264 EventHelper::new("hub_deregister_module")
265 .add_attribute("module", module)
266 .get(),
267 ))
268}
269
270fn execute_migrate_contracts(
271 deps: DepsMut,
272 env: Env,
273 info: MessageInfo,
274 code_id: u64,
275 contract_address: String,
276 msg: Binary,
277) -> Result<Response, ContractError> {
278 let operators = OPERATORS.may_load(deps.storage)?;
279 let config = CONFIG.load(deps.storage)?;
280
281 check_admin_privileges(
282 &info.sender,
283 &env.contract.address,
284 &config.admin,
285 None,
286 operators,
287 )?;
288
289 let contract_addr = deps.api.addr_validate(&contract_address)?;
290
291 let msg = WasmMsg::Migrate {
292 contract_addr: contract_addr.to_string(),
293 new_code_id: code_id,
294 msg,
295 };
296
297 Ok(ResponseHelper::new_module("hub", "migrate_contracts")
298 .add_message(msg)
299 .add_event(
300 EventHelper::new("hub_migrate_contracts")
301 .add_attribute("code_id", code_id.to_string())
302 .add_attribute("contract_address", contract_address)
303 .get(),
304 ))
305}
306
307#[cfg_attr(not(feature = "library"), entry_point)]
308pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
309 match msg {
310 QueryMsg::Config {} => to_binary(&query_config(deps)?),
311 QueryMsg::ModuleAddress { module } => to_binary(&query_module_address(deps, module)?),
312 QueryMsg::Modules { start_after, limit } => {
313 to_binary(&query_modules(deps, start_after, limit)?)
314 }
315 QueryMsg::Operators {} => to_binary(&query_operators(deps)?),
316 }
317}
318
319fn query_config(deps: Deps) -> StdResult<ResponseWrapper<ConfigResponse>> {
320 let config = CONFIG.load(deps.storage)?;
321 let hub_info = HUB_INFO.load(deps.storage)?;
322 Ok(ResponseWrapper::new(
323 "config",
324 ConfigResponse {
325 admin: config.admin.to_string(),
326 hub_info,
327 },
328 ))
329}
330
331fn query_module_address(deps: Deps, module: String) -> StdResult<ResponseWrapper<String>> {
332 let addr = MODULES.load(deps.storage, module.to_string())?;
333 Ok(ResponseWrapper::new("module_address", addr.to_string()))
334}
335
336fn query_modules(
337 deps: Deps,
338 start_after: Option<String>,
339 limit: Option<u8>,
340) -> StdResult<ResponseWrapper<Vec<ModulesResponse>>> {
341 let limit = limit.unwrap_or(10) as usize;
342 let start = start_after.map(Bound::exclusive);
343
344 let modules = MODULES
345 .range(deps.storage, start, None, Order::Ascending)
346 .take(limit)
347 .map(|item| {
348 let (name, address) = item.unwrap();
349 ModulesResponse {
350 name,
351 address: address.to_string(),
352 }
353 })
354 .collect::<Vec<ModulesResponse>>();
355
356 Ok(ResponseWrapper::new("modules", modules))
357}
358
359fn query_operators(deps: Deps) -> StdResult<ResponseWrapper<Vec<String>>> {
360 let addrs = OPERATORS.may_load(deps.storage)?;
361 let addrs = match addrs {
362 Some(addrs) => addrs.iter().map(|a| a.to_string()).collect(),
363 None => vec![],
364 };
365 Ok(ResponseWrapper::new("operators", addrs))
366}
367
368#[cfg_attr(not(feature = "library"), entry_point)]
369pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
370 let module_id = MODULE_ID.load(deps.storage)?;
373
374 if msg.id != module_id {
376 return Err(ContractError::InvalidReplyID {});
377 };
378
379 handle_module_instantiate_reply(deps, msg)
381}
382
383fn handle_module_instantiate_reply(deps: DepsMut, msg: Reply) -> Result<Response, ContractError> {
384 let reply = parse_reply_instantiate_data(msg);
385
386 let module_to_register = MODULE_TO_REGISTER.load(deps.storage)?;
388
389 match reply {
390 Ok(res) => {
391 MODULES.save(
392 deps.storage,
393 module_to_register.clone(),
394 &Addr::unchecked(res.contract_address),
395 )?;
396 Ok(Response::default().add_attribute(
397 "action",
398 format!("instantiate_{}_module_reply", module_to_register),
399 ))
400 }
401 Err(_) => Err(ContractError::ModuleInstantiateError {
402 module: module_to_register.to_string(),
403 }),
404 }
405}
406
407#[cfg_attr(not(feature = "library"), entry_point)]
408pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
409 let version: Version = CONTRACT_VERSION.parse()?;
410 let contract_version: ContractVersion = get_contract_version(deps.storage)?;
411 let storage_version: Version = contract_version.version.parse()?;
412
413 if contract_version.contract != CONTRACT_NAME {
414 return Err(
415 StdError::generic_err("New version name should match the current version").into(),
416 );
417 }
418 if storage_version >= version {
419 return Err(
420 StdError::generic_err("New version cannot be smaller than current version").into(),
421 );
422 }
423
424 set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
425
426 Ok(Response::default())
427}