1use std::{collections::HashSet, env};
2
3#[cfg(not(feature = "library"))]
4use cosmwasm_std::entry_point;
5use cosmwasm_std::{
6 to_json_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Reply, Response,
7 StdResult, SubMsg, WasmMsg,
8};
9use cw2::set_contract_version;
10use dao_interface::{
11 query::SubDao,
12 state::{ModuleInstantiateCallback, ProposalModule},
13};
14
15use crate::{
16 error::ContractError,
17 msg::{ExecuteMsg, InstantiateMsg, MigrateV1ToV2, QueryMsg},
18 state::{CORE_ADDR, MODULES_ADDRS, TEST_STATE},
19 types::{
20 CodeIdPair, MigrationMsgs, MigrationParams, ModulesAddrs, TestState, V1CodeIds, V2CodeIds,
21 },
22 utils::state_queries::{
23 query_proposal_count_v1, query_proposal_count_v2, query_proposal_v1, query_proposal_v2,
24 query_single_voting_power_v1, query_single_voting_power_v2, query_total_voting_power_v1,
25 query_total_voting_power_v2,
26 },
27};
28
29pub(crate) const CONTRACT_NAME: &str = "crates.io:dao-migrator";
30pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
31
32pub(crate) const V1_V2_REPLY_ID: u64 = 1;
33
34#[cfg_attr(not(feature = "library"), entry_point)]
35pub fn instantiate(
36 deps: DepsMut,
37 env: Env,
38 info: MessageInfo,
39 msg: InstantiateMsg,
40) -> Result<Response, ContractError> {
41 set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
42 CORE_ADDR.save(deps.storage, &info.sender)?;
43
44 Ok(
45 Response::default().set_data(to_json_binary(&ModuleInstantiateCallback {
46 msgs: vec![WasmMsg::Execute {
47 contract_addr: env.contract.address.to_string(),
48 msg: to_json_binary(&MigrateV1ToV2 {
49 sub_daos: msg.sub_daos,
50 migration_params: msg.migration_params,
51 v1_code_ids: msg.v1_code_ids,
52 v2_code_ids: msg.v2_code_ids,
53 })?,
54 funds: vec![],
55 }
56 .into()],
57 })?),
58 )
59}
60
61#[cfg_attr(not(feature = "library"), entry_point)]
62pub fn execute(
63 deps: DepsMut,
64 env: Env,
65 info: MessageInfo,
66 msg: ExecuteMsg,
67) -> Result<Response, ContractError> {
68 execute_migration_v1_v2(
69 deps,
70 env,
71 info,
72 msg.sub_daos,
73 msg.migration_params,
74 msg.v1_code_ids,
75 msg.v2_code_ids,
76 )
77}
78
79fn execute_migration_v1_v2(
80 deps: DepsMut,
81 env: Env,
82 info: MessageInfo,
83 sub_daos: Vec<SubDao>,
84 migration_params: MigrationParams,
85 v1_code_ids: V1CodeIds,
86 v2_code_ids: V2CodeIds,
87) -> Result<Response, ContractError> {
88 if info.sender != CORE_ADDR.load(deps.storage)? {
89 return Err(ContractError::Unauthorized {});
90 }
91
92 let mut uniq = HashSet::new();
94 if !migration_params
95 .proposal_params
96 .iter()
97 .all(|(addr, _)| uniq.insert(addr))
98 {
99 return Err(ContractError::DuplicateProposalParams);
100 }
101
102 let proposal_pairs: Vec<(String, CodeIdPair)> = migration_params
104 .proposal_params
105 .clone()
106 .into_iter()
107 .map(|(addr, proposal_params)| {
108 (
109 addr,
110 CodeIdPair::new(
111 v1_code_ids.proposal_single,
112 v2_code_ids.proposal_single,
113 MigrationMsgs::DaoProposalSingle(
114 dao_proposal_single::msg::MigrateMsg::FromV1 {
115 close_proposal_on_execution_failure: proposal_params
116 .close_proposal_on_execution_failure,
117 pre_propose_info: proposal_params.pre_propose_info,
118 veto: proposal_params.veto,
119 },
120 ),
121 ),
122 )
123 })
124 .collect(); let voting_pairs: Vec<CodeIdPair> = vec![
126 CodeIdPair::new(
127 v1_code_ids.cw4_voting,
128 v2_code_ids.cw4_voting,
129 MigrationMsgs::DaoVotingCw4(dao_voting_cw4::msg::MigrateMsg {}),
130 ), CodeIdPair::new(
132 v1_code_ids.cw20_staked_balances_voting,
133 v2_code_ids.cw20_staked_balances_voting,
134 MigrationMsgs::DaoVotingCw20Staked(dao_voting_cw20_staked::msg::MigrateMsg {}),
135 ), ];
137 let staking_pair = CodeIdPair::new(
138 v1_code_ids.cw20_stake,
139 v2_code_ids.cw20_stake,
140 MigrationMsgs::Cw20Stake(cw20_stake::msg::MigrateMsg::FromV1 {}),
141 ); let mut msgs: Vec<CosmosMsg> = vec![];
144 let mut modules_addrs = ModulesAddrs::default();
145
146 let voting_module: Addr = deps.querier.query_wasm_smart(
150 info.sender.clone(),
151 &dao_interface::msg::QueryMsg::VotingModule {},
152 )?;
153
154 let voting_code_id =
155 if let Ok(contract_info) = deps.querier.query_wasm_contract_info(voting_module.clone()) {
156 contract_info.code_id
157 } else {
158 return Err(ContractError::NoContractInfo {
160 address: voting_module.into(),
161 });
162 };
163
164 if let Some(voting_pair) = voting_pairs
165 .into_iter()
166 .find(|x| x.v1_code_id == voting_code_id)
167 {
168 msgs.push(
169 WasmMsg::Migrate {
170 contract_addr: voting_module.to_string(),
171 new_code_id: voting_pair.v2_code_id,
172 msg: to_json_binary(&voting_pair.migrate_msg).unwrap(),
173 }
174 .into(),
175 );
176 modules_addrs.voting = Some(voting_module.clone());
177
178 if let MigrationMsgs::DaoVotingCw20Staked(_) = voting_pair.migrate_msg {
181 if !migration_params
182 .migrate_stake_cw20_manager
183 .unwrap_or_default()
184 {
185 return Err(ContractError::DontMigrateCw20);
186 }
187
188 let cw20_staked_addr: Addr = deps.querier.query_wasm_smart(
189 voting_module,
190 &cw20_staked_balance_voting_v1::msg::QueryMsg::StakingContract {},
191 )?;
192
193 let c20_staked_code_id = if let Ok(contract_info) = deps
194 .querier
195 .query_wasm_contract_info(cw20_staked_addr.clone())
196 {
197 contract_info.code_id
198 } else {
199 return Err(ContractError::NoContractInfo {
201 address: cw20_staked_addr.into(),
202 });
203 };
204
205 if c20_staked_code_id != staking_pair.v1_code_id {
207 return Err(ContractError::CantMigrateModule {
208 code_id: c20_staked_code_id,
209 });
210 }
211
212 msgs.push(
213 WasmMsg::Migrate {
214 contract_addr: cw20_staked_addr.to_string(),
215 new_code_id: staking_pair.v2_code_id,
216 msg: to_json_binary(&staking_pair.migrate_msg).unwrap(),
217 }
218 .into(),
219 );
220 }
221 } else {
222 return Err(ContractError::VotingModuleNotFound);
223 }
224
225 let proposal_modules: Vec<ProposalModule> = deps.querier.query_wasm_smart(
230 info.sender.clone(),
231 &dao_interface::msg::QueryMsg::ProposalModules {
232 start_after: None,
233 limit: None,
234 },
235 )?;
236
237 if proposal_modules.len() - 1 != (proposal_pairs.len()) {
239 return Err(ContractError::MigrationParamsNotEqualProposalModulesLength);
240 }
241
242 proposal_modules
245 .iter()
246 .try_for_each(|module| -> Result<(), ContractError> {
247 if module.address == env.contract.address {
249 return Ok(());
250 }
251
252 let proposal_pair = proposal_pairs
253 .iter()
254 .find(|(addr, _)| addr == module.address.as_str())
255 .ok_or(ContractError::ProposalModuleNotFoundInParams {
256 addr: module.address.clone().into(),
257 })?
258 .1
259 .clone();
260
261 let proposal_code_id = if let Ok(contract_info) = deps
263 .querier
264 .query_wasm_contract_info(module.address.clone())
265 {
266 Ok(contract_info.code_id)
267 } else {
268 Err(ContractError::NoContractInfo {
270 address: module.address.clone().into(),
271 })
272 }?;
273
274 if proposal_code_id == proposal_pair.v1_code_id {
276 msgs.push(
277 WasmMsg::Migrate {
278 contract_addr: module.address.to_string(),
279 new_code_id: proposal_pair.v2_code_id,
280 msg: to_json_binary(&proposal_pair.migrate_msg).unwrap(),
281 }
282 .into(),
283 );
284 modules_addrs.proposals.push(module.address.clone());
285 Ok(())
286 } else {
287 Err(ContractError::CantMigrateModule {
289 code_id: proposal_code_id,
290 })
291 }?;
292
293 Ok(())
294 })?;
295
296 modules_addrs.verify()?;
300 MODULES_ADDRS.save(deps.storage, &modules_addrs)?;
301 let state = query_state_v1(deps.as_ref(), modules_addrs)?;
303 TEST_STATE.save(deps.storage, &state)?;
304
305 msgs.push(
307 WasmMsg::Execute {
308 contract_addr: info.sender.to_string(),
309 msg: to_json_binary(&dao_interface::msg::ExecuteMsg::UpdateSubDaos {
310 to_add: sub_daos,
311 to_remove: vec![],
312 })?,
313 funds: vec![],
314 }
315 .into(),
316 );
317
318 let proposal_hook_msg = SubMsg::reply_on_success(
320 WasmMsg::Execute {
321 contract_addr: info.sender.to_string(),
322 msg: to_json_binary(&dao_interface::msg::ExecuteMsg::ExecuteProposalHook { msgs })?,
323 funds: vec![],
324 },
325 V1_V2_REPLY_ID,
326 );
327
328 Ok(Response::default().add_submessage(proposal_hook_msg))
329}
330
331#[cfg_attr(not(feature = "library"), entry_point)]
332pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
333 match msg {}
334}
335
336#[cfg_attr(not(feature = "library"), entry_point)]
337pub fn reply(deps: DepsMut, env: Env, reply: Reply) -> Result<Response, ContractError> {
338 match reply.id {
339 V1_V2_REPLY_ID => {
340 let core_addr = CORE_ADDR.load(deps.storage)?;
341 test_state(deps.as_ref())?;
343
344 let remove_msg = WasmMsg::Execute {
348 contract_addr: core_addr.to_string(),
349 msg: to_json_binary(&dao_interface::msg::ExecuteMsg::ExecuteProposalHook {
350 msgs: vec![WasmMsg::Execute {
351 contract_addr: core_addr.to_string(),
352 msg: to_json_binary(
353 &dao_interface::msg::ExecuteMsg::UpdateProposalModules {
354 to_add: vec![],
355 to_disable: vec![env.contract.address.to_string()],
356 },
357 )?,
358 funds: vec![],
359 }
360 .into()],
361 })?,
362 funds: vec![],
363 };
364
365 Ok(Response::default()
366 .add_message(remove_msg)
367 .add_attribute("action", "migrate")
368 .add_attribute("status", "success"))
369 }
370 _ => Err(ContractError::UnrecognisedReplyId),
371 }
372}
373
374fn query_state_v1(deps: Deps, module_addrs: ModulesAddrs) -> Result<TestState, ContractError> {
375 let proposal_counts = query_proposal_count_v1(deps, module_addrs.proposals.clone())?;
376 let (proposals, sample_proposal_data) = query_proposal_v1(deps, module_addrs.proposals)?;
377 let total_voting_power = query_total_voting_power_v1(
378 deps,
379 module_addrs.voting.clone().unwrap(),
380 sample_proposal_data.start_height,
381 )?;
382 let single_voting_power = query_single_voting_power_v1(
383 deps,
384 module_addrs.voting.unwrap(),
385 sample_proposal_data.proposer,
386 sample_proposal_data.start_height,
387 )?;
388
389 Ok(TestState {
390 proposal_counts,
391 proposals,
392 total_voting_power,
393 single_voting_power,
394 })
395}
396
397fn query_state_v2(deps: Deps, module_addrs: ModulesAddrs) -> Result<TestState, ContractError> {
398 let proposal_counts = query_proposal_count_v2(deps, module_addrs.proposals.clone())?;
399 let (proposals, sample_proposal_data) =
400 query_proposal_v2(deps, module_addrs.proposals.clone())?;
401 let total_voting_power = query_total_voting_power_v2(
402 deps,
403 module_addrs.voting.clone().unwrap(),
404 sample_proposal_data.start_height,
405 )?;
406 let single_voting_power = query_single_voting_power_v2(
407 deps,
408 module_addrs.voting.unwrap(),
409 sample_proposal_data.proposer,
410 sample_proposal_data.start_height,
411 )?;
412
413 Ok(TestState {
414 proposal_counts,
415 proposals,
416 total_voting_power,
417 single_voting_power,
418 })
419}
420
421fn test_state(deps: Deps) -> Result<(), ContractError> {
422 let old_state = TEST_STATE.load(deps.storage)?;
423 let modules_addrs = MODULES_ADDRS.load(deps.storage)?;
424 let new_state = query_state_v2(deps, modules_addrs)?;
425
426 if new_state == old_state {
427 Ok(())
428 } else {
429 Err(ContractError::TestFailed)
430 }
431}