cw_core/
contract.rs

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
27// version info for migration info
28const 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        // If no admin is specified, the contract is its own admin.
58        .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    // No actions can be performed while the DAO is paused.
96    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    // Only the core contract may call this method.
146    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    // Check if the sender is the DAO Admin
168    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    // Check that the message has come from one of the proposal modules
183    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        // If no admin set to default of the contract. This allows the
213        // contract to later set a new admin via governance.
214        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    // Check that there is indeed a nomination to withdraw.
255    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    // We incur some gas costs by having the config's fields in the
279    // response. This has the benefit that it makes it reasonably
280    // simple to ask "when did this field in the config change" by
281    // running something like `junod query txs --events
282    // 'wasm._contract_address=core&wasm.name=name'`.
283    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 we removed all of our proposal modules and we are not adding
333    // any this operation would result in no proposal modules being
334    // present.
335    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    // This query is will run out of gas due to the size of the
518    // returned message before it runs out of compute so taking a
519    // limit here is still nice. As removes happen in constant time
520    // the contract is still recoverable if too many items end up in
521    // here.
522    //
523    // Further, as the `keys` method on a map returns an iterator it
524    // is possible (though implementation dependent) that new keys are
525    // loaded on demand as the iterator is moved. Should this be the
526    // case we are only paying for what we need here.
527    //
528    // Even if this does lock up one can determine the existing
529    // proposal modules by looking at past transactions on chain.
530    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    // Don't do any state migrations.
682    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            // Make sure a bug in instantiation isn't causing us to
701            // make more than one voting module.
702            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}