Skip to main content

komple_framework_hub_module/
contract.rs

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
28// version info for migration info
29const 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    // Return error if instantiate data is not sent
44    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    // Save fee module info for Marbu if exists
60    // This comes from Marbu Controller on Hub creation
61    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    // Get the latest module reply id
142    let module_id = (MODULE_ID.load(deps.storage)?) + 1;
143
144    // Register message to instantiate the module
145    // Admin is set as the hub's admin
146    // Additional data is sent to the module
147    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(&register_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 name will be loaded in reply handler for saving
168    // the correct module name to storage
169    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    // Create a message to disable execute messages on module
248    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    // Create a message to set contract's admin as None
255    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    // Get the last module id
371    // This is used as the reply id
372    let module_id = MODULE_ID.load(deps.storage)?;
373
374    // Check if the reply id is the same
375    if msg.id != module_id {
376        return Err(ContractError::InvalidReplyID {});
377    };
378
379    // Handle the registration
380    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    // Get the module for registering
387    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}