1#[cfg(not(feature = "library"))]
2use cosmwasm_std::entry_point;
3use cosmwasm_std::{
4 to_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo, Reply, Response,
5 StdResult, SubMsg,
6};
7use cw2::{get_contract_version, set_contract_version};
8use cw_storage_plus::Map;
9use cw_utils::{parse_reply_instantiate_data, Duration};
10
11use cw_core_interface::voting;
12use cw_paginate_storage::{paginate_map, paginate_map_keys};
13
14use crate::error::ContractError;
15use crate::msg::{
16 ExecuteMsg, InitialItem, InstantiateMsg, MigrateMsg, ModuleInstantiateInfo, QueryMsg,
17};
18use crate::query::{
19 AdminNominationResponse, Cw20BalanceResponse, DumpStateResponse, GetItemResponse,
20 PauseInfoResponse,
21};
22use crate::state::{
23 Config, ADMIN, CONFIG, CW20_LIST, CW721_LIST, ITEMS, NOMINATED_ADMIN, PAUSED, PROPOSAL_MODULES,
24 VOTING_MODULE,
25};
26
27const CONTRACT_NAME: &str = "crates.io:cw-core";
29const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
30
31const PROPOSAL_MODULE_REPLY_ID: u64 = 0;
32const VOTE_MODULE_INSTANTIATE_REPLY_ID: u64 = 1;
33const VOTE_MODULE_UPDATE_REPLY_ID: u64 = 2;
34
35#[cfg_attr(not(feature = "library"), entry_point)]
36pub fn instantiate(
37 deps: DepsMut,
38 env: Env,
39 info: MessageInfo,
40 msg: InstantiateMsg,
41) -> Result<Response, ContractError> {
42 set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
43
44 let config = Config {
45 name: msg.name,
46 description: msg.description,
47 image_url: msg.image_url,
48 automatically_add_cw20s: msg.automatically_add_cw20s,
49 automatically_add_cw721s: msg.automatically_add_cw721s,
50 };
51 CONFIG.save(deps.storage, &config)?;
52
53 let admin = msg
54 .admin
55 .map(|human| deps.api.addr_validate(&human))
56 .transpose()?
57 .unwrap_or_else(|| env.contract.address.clone());
59 ADMIN.save(deps.storage, &admin)?;
60
61 let vote_module_msg = msg
62 .voting_module_instantiate_info
63 .into_wasm_msg(env.contract.address.clone());
64 let vote_module_msg: SubMsg<Empty> =
65 SubMsg::reply_on_success(vote_module_msg, VOTE_MODULE_INSTANTIATE_REPLY_ID);
66
67 let proposal_module_msgs: Vec<SubMsg<Empty>> = msg
68 .proposal_modules_instantiate_info
69 .into_iter()
70 .map(|info| info.into_wasm_msg(env.contract.address.clone()))
71 .map(|wasm| SubMsg::reply_on_success(wasm, PROPOSAL_MODULE_REPLY_ID))
72 .collect();
73 if proposal_module_msgs.is_empty() {
74 return Err(ContractError::NoProposalModule {});
75 }
76
77 for InitialItem { key, value } in msg.initial_items.unwrap_or_default() {
78 ITEMS.save(deps.storage, key, &value)?;
79 }
80
81 Ok(Response::new()
82 .add_attribute("action", "instantiate")
83 .add_attribute("sender", info.sender)
84 .add_submessage(vote_module_msg)
85 .add_submessages(proposal_module_msgs))
86}
87
88#[cfg_attr(not(feature = "library"), entry_point)]
89pub fn execute(
90 deps: DepsMut,
91 env: Env,
92 info: MessageInfo,
93 msg: ExecuteMsg,
94) -> Result<Response, ContractError> {
95 if let Some(expiration) = PAUSED.may_load(deps.storage)? {
97 if !expiration.is_expired(&env.block) {
98 return Err(ContractError::Paused {});
99 }
100 }
101
102 match msg {
103 ExecuteMsg::ExecuteAdminMsgs { msgs } => {
104 execute_admin_msgs(deps.as_ref(), info.sender, msgs)
105 }
106 ExecuteMsg::ExecuteProposalHook { msgs } => {
107 execute_proposal_hook(deps.as_ref(), info.sender, msgs)
108 }
109 ExecuteMsg::Pause { duration } => execute_pause(deps, env, info.sender, duration),
110 ExecuteMsg::Receive(_) => execute_receive_cw20(deps, info.sender),
111 ExecuteMsg::ReceiveNft(_) => execute_receive_cw721(deps, info.sender),
112 ExecuteMsg::RemoveItem { key } => execute_remove_item(deps, env, info.sender, key),
113 ExecuteMsg::SetItem { key, addr } => execute_set_item(deps, env, info.sender, key, addr),
114 ExecuteMsg::UpdateConfig { config } => {
115 execute_update_config(deps, env, info.sender, config)
116 }
117 ExecuteMsg::UpdateCw20List { to_add, to_remove } => {
118 execute_update_cw20_list(deps, env, info.sender, to_add, to_remove)
119 }
120 ExecuteMsg::UpdateCw721List { to_add, to_remove } => {
121 execute_update_cw721_list(deps, env, info.sender, to_add, to_remove)
122 }
123 ExecuteMsg::UpdateVotingModule { module } => {
124 execute_update_voting_module(env, info.sender, module)
125 }
126 ExecuteMsg::UpdateProposalModules { to_add, to_remove } => {
127 execute_update_proposal_modules(deps, env, info.sender, to_add, to_remove)
128 }
129 ExecuteMsg::NominateAdmin { admin } => {
130 execute_nominate_admin(deps, env, info.sender, admin)
131 }
132 ExecuteMsg::AcceptAdminNomination {} => execute_accept_admin_nomination(deps, info.sender),
133 ExecuteMsg::WithdrawAdminNomination {} => {
134 execute_withdraw_admin_nomination(deps, info.sender)
135 }
136 }
137}
138
139pub fn execute_pause(
140 deps: DepsMut,
141 env: Env,
142 sender: Addr,
143 pause_duration: Duration,
144) -> Result<Response, ContractError> {
145 if sender != env.contract.address {
147 return Err(ContractError::Unauthorized {});
148 }
149
150 let until = pause_duration.after(&env.block);
151
152 PAUSED.save(deps.storage, &until)?;
153
154 Ok(Response::new()
155 .add_attribute("action", "execute_pause")
156 .add_attribute("sender", sender)
157 .add_attribute("until", until.to_string()))
158}
159
160pub fn execute_admin_msgs(
161 deps: Deps,
162 sender: Addr,
163 msgs: Vec<CosmosMsg<Empty>>,
164) -> Result<Response, ContractError> {
165 let admin = ADMIN.load(deps.storage)?;
166
167 if sender != admin {
169 return Err(ContractError::Unauthorized {});
170 }
171
172 Ok(Response::default()
173 .add_attribute("action", "execute_admin_msgs")
174 .add_messages(msgs))
175}
176
177pub fn execute_proposal_hook(
178 deps: Deps,
179 sender: Addr,
180 msgs: Vec<CosmosMsg<Empty>>,
181) -> Result<Response, ContractError> {
182 if !PROPOSAL_MODULES.has(deps.storage, sender) {
184 return Err(ContractError::Unauthorized {});
185 }
186
187 Ok(Response::default()
188 .add_attribute("action", "execute_proposal_hook")
189 .add_messages(msgs))
190}
191
192pub fn execute_nominate_admin(
193 deps: DepsMut,
194 env: Env,
195 sender: Addr,
196 nomination: Option<String>,
197) -> Result<Response, ContractError> {
198 let nomination = nomination.map(|h| deps.api.addr_validate(&h)).transpose()?;
199
200 let current_admin = ADMIN.load(deps.storage)?;
201 if current_admin != sender {
202 return Err(ContractError::Unauthorized {});
203 }
204
205 let current_nomination = NOMINATED_ADMIN.may_load(deps.storage)?;
206 if current_nomination.is_some() {
207 return Err(ContractError::PendingNomination {});
208 }
209
210 match &nomination {
211 Some(nomination) => NOMINATED_ADMIN.save(deps.storage, nomination)?,
212 None => ADMIN.save(deps.storage, &env.contract.address)?,
215 }
216
217 Ok(Response::default()
218 .add_attribute("action", "execute_nominate_admin")
219 .add_attribute(
220 "nomination",
221 nomination
222 .map(|n| n.to_string())
223 .unwrap_or_else(|| "None".to_string()),
224 ))
225}
226
227pub fn execute_accept_admin_nomination(
228 deps: DepsMut,
229 sender: Addr,
230) -> Result<Response, ContractError> {
231 let nomination = NOMINATED_ADMIN
232 .may_load(deps.storage)?
233 .ok_or(ContractError::NoAdminNomination {})?;
234 if sender != nomination {
235 return Err(ContractError::Unauthorized {});
236 }
237 NOMINATED_ADMIN.remove(deps.storage);
238 ADMIN.save(deps.storage, &nomination)?;
239
240 Ok(Response::default()
241 .add_attribute("action", "execute_accept_admin_nomination")
242 .add_attribute("new_admin", sender))
243}
244
245pub fn execute_withdraw_admin_nomination(
246 deps: DepsMut,
247 sender: Addr,
248) -> Result<Response, ContractError> {
249 let admin = ADMIN.load(deps.storage)?;
250 if admin != sender {
251 return Err(ContractError::Unauthorized {});
252 }
253
254 let current_nomination = NOMINATED_ADMIN.may_load(deps.storage)?;
256 if current_nomination.is_none() {
257 return Err(ContractError::NoAdminNomination {});
258 }
259
260 NOMINATED_ADMIN.remove(deps.storage);
261
262 Ok(Response::default()
263 .add_attribute("action", "execute_withdraw_admin_nomination")
264 .add_attribute("sender", sender))
265}
266
267pub fn execute_update_config(
268 deps: DepsMut,
269 env: Env,
270 sender: Addr,
271 config: Config,
272) -> Result<Response, ContractError> {
273 if sender != env.contract.address {
274 return Err(ContractError::Unauthorized {});
275 }
276
277 CONFIG.save(deps.storage, &config)?;
278 Ok(Response::default()
284 .add_attribute("action", "execute_update_config")
285 .add_attribute("name", config.name)
286 .add_attribute("description", config.description)
287 .add_attribute(
288 "image_url",
289 config.image_url.unwrap_or_else(|| "None".to_string()),
290 ))
291}
292
293pub fn execute_update_voting_module(
294 env: Env,
295 sender: Addr,
296 module: ModuleInstantiateInfo,
297) -> Result<Response, ContractError> {
298 if env.contract.address != sender {
299 return Err(ContractError::Unauthorized {});
300 }
301
302 let wasm = module.into_wasm_msg(env.contract.address);
303 let submessage = SubMsg::reply_on_success(wasm, VOTE_MODULE_UPDATE_REPLY_ID);
304
305 Ok(Response::default()
306 .add_attribute("action", "execute_update_voting_module")
307 .add_submessage(submessage))
308}
309
310pub fn execute_update_proposal_modules(
311 deps: DepsMut,
312 env: Env,
313 sender: Addr,
314 to_add: Vec<ModuleInstantiateInfo>,
315 to_remove: Vec<String>,
316) -> Result<Response, ContractError> {
317 if env.contract.address != sender {
318 return Err(ContractError::Unauthorized {});
319 }
320
321 for addr in to_remove {
322 let addr = deps.api.addr_validate(&addr)?;
323 PROPOSAL_MODULES.remove(deps.storage, addr);
324 }
325
326 let to_add: Vec<SubMsg<Empty>> = to_add
327 .into_iter()
328 .map(|info| info.into_wasm_msg(env.contract.address.clone()))
329 .map(|wasm| SubMsg::reply_on_success(wasm, PROPOSAL_MODULE_REPLY_ID))
330 .collect();
331
332 if PROPOSAL_MODULES
336 .keys_raw(deps.storage, None, None, cosmwasm_std::Order::Ascending)
337 .next()
338 .is_none()
339 && to_add.is_empty()
340 {
341 return Err(ContractError::NoProposalModule {});
342 }
343
344 Ok(Response::default()
345 .add_attribute("action", "execute_update_proposal_modules")
346 .add_submessages(to_add))
347}
348
349fn do_update_addr_list(
350 deps: DepsMut,
351 map: Map<Addr, Empty>,
352 to_add: Vec<String>,
353 to_remove: Vec<String>,
354) -> Result<(), ContractError> {
355 let to_add = to_add
356 .into_iter()
357 .map(|a| deps.api.addr_validate(&a))
358 .collect::<Result<Vec<_>, _>>()?;
359
360 let to_remove = to_remove
361 .into_iter()
362 .map(|a| deps.api.addr_validate(&a))
363 .collect::<Result<Vec<_>, _>>()?;
364
365 for addr in to_add {
366 map.save(deps.storage, addr, &Empty {})?;
367 }
368 for addr in to_remove {
369 map.remove(deps.storage, addr);
370 }
371
372 Ok(())
373}
374
375pub fn execute_update_cw20_list(
376 deps: DepsMut,
377 env: Env,
378 sender: Addr,
379 to_add: Vec<String>,
380 to_remove: Vec<String>,
381) -> Result<Response, ContractError> {
382 if env.contract.address != sender {
383 return Err(ContractError::Unauthorized {});
384 }
385 do_update_addr_list(deps, CW20_LIST, to_add, to_remove)?;
386 Ok(Response::default().add_attribute("action", "update_cw20_list"))
387}
388
389pub fn execute_update_cw721_list(
390 deps: DepsMut,
391 env: Env,
392 sender: Addr,
393 to_add: Vec<String>,
394 to_remove: Vec<String>,
395) -> Result<Response, ContractError> {
396 if env.contract.address != sender {
397 return Err(ContractError::Unauthorized {});
398 }
399 do_update_addr_list(deps, CW721_LIST, to_add, to_remove)?;
400 Ok(Response::default().add_attribute("action", "update_cw721_list"))
401}
402
403pub fn execute_set_item(
404 deps: DepsMut,
405 env: Env,
406 sender: Addr,
407 key: String,
408 value: String,
409) -> Result<Response, ContractError> {
410 if env.contract.address != sender {
411 return Err(ContractError::Unauthorized {});
412 }
413
414 ITEMS.save(deps.storage, key.clone(), &value)?;
415 Ok(Response::default()
416 .add_attribute("action", "execute_set_item")
417 .add_attribute("key", key)
418 .add_attribute("addr", value))
419}
420
421pub fn execute_remove_item(
422 deps: DepsMut,
423 env: Env,
424 sender: Addr,
425 key: String,
426) -> Result<Response, ContractError> {
427 if env.contract.address != sender {
428 return Err(ContractError::Unauthorized {});
429 }
430
431 if ITEMS.has(deps.storage, key.clone()) {
432 ITEMS.remove(deps.storage, key.clone());
433 Ok(Response::default()
434 .add_attribute("action", "execute_remove_item")
435 .add_attribute("key", key))
436 } else {
437 Err(ContractError::KeyMissing {})
438 }
439}
440
441pub fn execute_receive_cw20(deps: DepsMut, sender: Addr) -> Result<Response, ContractError> {
442 let config = CONFIG.load(deps.storage)?;
443 if !config.automatically_add_cw20s {
444 Ok(Response::new())
445 } else {
446 CW20_LIST.save(deps.storage, sender.clone(), &Empty {})?;
447 Ok(Response::new()
448 .add_attribute("action", "receive_cw20")
449 .add_attribute("token", sender))
450 }
451}
452
453pub fn execute_receive_cw721(deps: DepsMut, sender: Addr) -> Result<Response, ContractError> {
454 let config = CONFIG.load(deps.storage)?;
455 if !config.automatically_add_cw721s {
456 Ok(Response::new())
457 } else {
458 CW721_LIST.save(deps.storage, sender.clone(), &Empty {})?;
459 Ok(Response::new()
460 .add_attribute("action", "receive_cw721")
461 .add_attribute("token", sender))
462 }
463}
464
465#[cfg_attr(not(feature = "library"), entry_point)]
466pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
467 match msg {
468 QueryMsg::Admin {} => query_admin(deps),
469 QueryMsg::AdminNomination {} => query_admin_nomination(deps),
470 QueryMsg::Config {} => query_config(deps),
471 QueryMsg::Cw20TokenList { start_at, limit } => query_cw20_list(deps, start_at, limit),
472 QueryMsg::Cw20Balances { start_at, limit } => {
473 query_cw20_balances(deps, env, start_at, limit)
474 }
475 QueryMsg::Cw721TokenList { start_at, limit } => query_cw721_list(deps, start_at, limit),
476 QueryMsg::DumpState {} => query_dump_state(deps, env),
477 QueryMsg::GetItem { key } => query_get_item(deps, key),
478 QueryMsg::Info {} => query_info(deps),
479 QueryMsg::ListItems { start_at, limit } => query_list_items(deps, start_at, limit),
480 QueryMsg::PauseInfo {} => query_paused(deps, env),
481 QueryMsg::ProposalModules { start_at, limit } => {
482 query_proposal_modules(deps, start_at, limit)
483 }
484 QueryMsg::TotalPowerAtHeight { height } => query_total_power_at_height(deps, height),
485 QueryMsg::VotingModule {} => query_voting_module(deps),
486 QueryMsg::VotingPowerAtHeight { address, height } => {
487 query_voting_power_at_height(deps, address, height)
488 }
489 }
490}
491
492pub fn query_admin(deps: Deps) -> StdResult<Binary> {
493 let admin = ADMIN.load(deps.storage)?;
494 to_binary(&admin)
495}
496
497pub fn query_admin_nomination(deps: Deps) -> StdResult<Binary> {
498 let nomination = NOMINATED_ADMIN.may_load(deps.storage)?;
499 to_binary(&AdminNominationResponse { nomination })
500}
501
502pub fn query_config(deps: Deps) -> StdResult<Binary> {
503 let config = CONFIG.load(deps.storage)?;
504 to_binary(&config)
505}
506
507pub fn query_voting_module(deps: Deps) -> StdResult<Binary> {
508 let voting_module = VOTING_MODULE.load(deps.storage)?;
509 to_binary(&voting_module)
510}
511
512pub fn query_proposal_modules(
513 deps: Deps,
514 start_at: Option<String>,
515 limit: Option<u32>,
516) -> StdResult<Binary> {
517 to_binary(&paginate_map_keys(
531 deps,
532 &PROPOSAL_MODULES,
533 start_at.map(|s| deps.api.addr_validate(&s)).transpose()?,
534 limit,
535 cosmwasm_std::Order::Descending,
536 )?)
537}
538
539fn get_pause_info(deps: Deps, env: Env) -> StdResult<PauseInfoResponse> {
540 Ok(match PAUSED.may_load(deps.storage)? {
541 Some(expiration) => {
542 if expiration.is_expired(&env.block) {
543 PauseInfoResponse::Unpaused {}
544 } else {
545 PauseInfoResponse::Paused { expiration }
546 }
547 }
548 None => PauseInfoResponse::Unpaused {},
549 })
550}
551
552pub fn query_paused(deps: Deps, env: Env) -> StdResult<Binary> {
553 to_binary(&get_pause_info(deps, env)?)
554}
555
556pub fn query_dump_state(deps: Deps, env: Env) -> StdResult<Binary> {
557 let admin = ADMIN.load(deps.storage)?;
558 let config = CONFIG.load(deps.storage)?;
559 let voting_module = VOTING_MODULE.load(deps.storage)?;
560 let proposal_modules = PROPOSAL_MODULES
561 .keys(deps.storage, None, None, cosmwasm_std::Order::Descending)
562 .collect::<Result<Vec<Addr>, _>>()?;
563 let pause_info = get_pause_info(deps, env)?;
564 let version = get_contract_version(deps.storage)?;
565 to_binary(&DumpStateResponse {
566 admin,
567 config,
568 version,
569 pause_info,
570 proposal_modules,
571 voting_module,
572 })
573}
574
575pub fn query_voting_power_at_height(
576 deps: Deps,
577 address: String,
578 height: Option<u64>,
579) -> StdResult<Binary> {
580 let voting_module = VOTING_MODULE.load(deps.storage)?;
581 let voting_power: voting::VotingPowerAtHeightResponse = deps.querier.query_wasm_smart(
582 voting_module,
583 &voting::Query::VotingPowerAtHeight { height, address },
584 )?;
585 to_binary(&voting_power)
586}
587
588pub fn query_total_power_at_height(deps: Deps, height: Option<u64>) -> StdResult<Binary> {
589 let voting_module = VOTING_MODULE.load(deps.storage)?;
590 let total_power: voting::TotalPowerAtHeightResponse = deps
591 .querier
592 .query_wasm_smart(voting_module, &voting::Query::TotalPowerAtHeight { height })?;
593 to_binary(&total_power)
594}
595
596pub fn query_get_item(deps: Deps, item: String) -> StdResult<Binary> {
597 let item = ITEMS.may_load(deps.storage, item)?;
598 to_binary(&GetItemResponse { item })
599}
600
601pub fn query_info(deps: Deps) -> StdResult<Binary> {
602 let info = cw2::get_contract_version(deps.storage)?;
603 to_binary(&cw_core_interface::voting::InfoResponse { info })
604}
605
606pub fn query_list_items(
607 deps: Deps,
608 start_at: Option<String>,
609 limit: Option<u32>,
610) -> StdResult<Binary> {
611 to_binary(&paginate_map(
612 deps,
613 &ITEMS,
614 start_at,
615 limit,
616 cosmwasm_std::Order::Descending,
617 )?)
618}
619
620pub fn query_cw20_list(
621 deps: Deps,
622 start_at: Option<String>,
623 limit: Option<u32>,
624) -> StdResult<Binary> {
625 to_binary(&paginate_map_keys(
626 deps,
627 &CW20_LIST,
628 start_at.map(|s| deps.api.addr_validate(&s)).transpose()?,
629 limit,
630 cosmwasm_std::Order::Descending,
631 )?)
632}
633
634pub fn query_cw721_list(
635 deps: Deps,
636 start_at: Option<String>,
637 limit: Option<u32>,
638) -> StdResult<Binary> {
639 to_binary(&paginate_map_keys(
640 deps,
641 &CW721_LIST,
642 start_at.map(|s| deps.api.addr_validate(&s)).transpose()?,
643 limit,
644 cosmwasm_std::Order::Descending,
645 )?)
646}
647
648pub fn query_cw20_balances(
649 deps: Deps,
650 env: Env,
651 start_at: Option<String>,
652 limit: Option<u32>,
653) -> StdResult<Binary> {
654 let addrs = paginate_map_keys(
655 deps,
656 &CW20_LIST,
657 start_at.map(|a| deps.api.addr_validate(&a)).transpose()?,
658 limit,
659 cosmwasm_std::Order::Descending,
660 )?;
661 let balances = addrs
662 .into_iter()
663 .map(|addr| {
664 let balance: cw20::BalanceResponse = deps.querier.query_wasm_smart(
665 addr.clone(),
666 &cw20::Cw20QueryMsg::Balance {
667 address: env.contract.address.to_string(),
668 },
669 )?;
670 Ok(Cw20BalanceResponse {
671 addr,
672 balance: balance.balance,
673 })
674 })
675 .collect::<StdResult<Vec<_>>>()?;
676 to_binary(&balances)
677}
678
679#[cfg_attr(not(feature = "library"), entry_point)]
680pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
681 Ok(Response::default())
683}
684
685#[cfg_attr(not(feature = "library"), entry_point)]
686pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
687 match msg.id {
688 PROPOSAL_MODULE_REPLY_ID => {
689 let res = parse_reply_instantiate_data(msg)?;
690 let prop_module_addr = deps.api.addr_validate(&res.contract_address)?;
691 PROPOSAL_MODULES.save(deps.storage, prop_module_addr, &Empty {})?;
692
693 Ok(Response::default().add_attribute("prop_module".to_string(), res.contract_address))
694 }
695 VOTE_MODULE_INSTANTIATE_REPLY_ID => {
696 let res = parse_reply_instantiate_data(msg)?;
697 let vote_module_addr = deps.api.addr_validate(&res.contract_address)?;
698 let current = VOTING_MODULE.may_load(deps.storage)?;
699
700 if current.is_some() {
703 return Err(ContractError::MultipleVotingModules {});
704 }
705
706 VOTING_MODULE.save(deps.storage, &vote_module_addr)?;
707
708 Ok(Response::default().add_attribute("voting_module", vote_module_addr))
709 }
710 VOTE_MODULE_UPDATE_REPLY_ID => {
711 let res = parse_reply_instantiate_data(msg)?;
712 let vote_module_addr = deps.api.addr_validate(&res.contract_address)?;
713
714 VOTING_MODULE.save(deps.storage, &vote_module_addr)?;
715
716 Ok(Response::default().add_attribute("voting_module", vote_module_addr))
717 }
718 _ => Err(ContractError::UnknownReplyID {}),
719 }
720}