Repository: DA0-DA0/dao-contracts
Pull request #314 (2022-05-28 23:27:38)

Add migrate method to contracts.
This will allow our contracts to be migrated by their admin. In the case of modules this is typically the `cw-core` contract.

codecov-commenter: # [Codecov](https://codecov.io/gh/DA0-DA0/dao-contracts/pull/314?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0) Report
> Merging [#314](https://codecov.io/gh/DA0-DA0/dao-contracts/pull/314?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0) (58629b6) into [main](https://codecov.io/gh/DA0-DA0/dao-contracts/commit/608ad08951323a3a6dde778c80872af704147fd8?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0) (608ad08) will **increase** coverage by `0.04%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##             main     #314      +/-   ##
==========================================
+ Coverage   96.70%   96.75%   +0.04%     
==========================================
  Files          36       36              
  Lines        8041     8158     +117     
==========================================
+ Hits         7776     7893     +117     
  Misses        265      265              
```


| [Impacted Files](https://codecov.io/gh/DA0-DA0/dao-contracts/pull/314?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0) | Coverage Δ | |
|---|---|---|
| [contracts/cw-core/src/contract.rs](https://codecov.io/gh/DA0-DA0/dao-contracts/pull/314/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0#diff-Y29udHJhY3RzL2N3LWNvcmUvc3JjL2NvbnRyYWN0LnJz) | `94.49% <100.00%> (+0.03%)` | :arrow_up: |
| [contracts/cw-core/src/tests.rs](https://codecov.io/gh/DA0-DA0/dao-contracts/pull/314/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0#diff-Y29udHJhY3RzL2N3LWNvcmUvc3JjL3Rlc3RzLnJz) | `100.00% <100.00%> (ø)` | |
| [contracts/cw-proposal-single/src/contract.rs](https://codecov.io/gh/DA0-DA0/dao-contracts/pull/314/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0#diff-Y29udHJhY3RzL2N3LXByb3Bvc2FsLXNpbmdsZS9zcmMvY29udHJhY3QucnM=) | `90.49% <100.00%> (+0.06%)` | :arrow_up: |
| [contracts/cw-proposal-single/src/tests.rs](https://codecov.io/gh/DA0-DA0/dao-contracts/pull/314/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0#diff-Y29udHJhY3RzL2N3LXByb3Bvc2FsLXNpbmdsZS9zcmMvdGVzdHMucnM=) | `98.39% <100.00%> (+0.02%)` | :arrow_up: |
| [...ntracts/cw20-staked-balance-voting/src/contract.rs](https://codecov.io/gh/DA0-DA0/dao-contracts/pull/314/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0#diff-Y29udHJhY3RzL2N3MjAtc3Rha2VkLWJhbGFuY2Utdm90aW5nL3NyYy9jb250cmFjdC5ycw==) | `94.31% <100.00%> (+0.06%)` | :arrow_up: |
| [contracts/cw20-staked-balance-voting/src/tests.rs](https://codecov.io/gh/DA0-DA0/dao-contracts/pull/314/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0#diff-Y29udHJhY3RzL2N3MjAtc3Rha2VkLWJhbGFuY2Utdm90aW5nL3NyYy90ZXN0cy5ycw==) | `100.00% <100.00%> (ø)` | |
| [contracts/cw4-voting/src/contract.rs](https://codecov.io/gh/DA0-DA0/dao-contracts/pull/314/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0#diff-Y29udHJhY3RzL2N3NC12b3Rpbmcvc3JjL2NvbnRyYWN0LnJz) | `94.94% <100.00%> (+0.10%)` | :arrow_up: |
| [contracts/cw4-voting/src/tests.rs](https://codecov.io/gh/DA0-DA0/dao-contracts/pull/314/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0#diff-Y29udHJhY3RzL2N3NC12b3Rpbmcvc3JjL3Rlc3RzLnJz) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/DA0-DA0/dao-contracts/pull/314?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/DA0-DA0/dao-contracts/pull/314?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0). Last update [608ad08...58629b6](https://codecov.io/gh/DA0-DA0/dao-contracts/pull/314?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0).


Patch:
diff --git a/contracts/cw-core/examples/schema.rs b/contracts/cw-core/examples/schema.rs
index 406f525d..2b27dc67 100644
--- a/contracts/cw-core/examples/schema.rs
+++ b/contracts/cw-core/examples/schema.rs
@@ -4,7 +4,7 @@ use std::fs::create_dir_all;
 use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for};
 use cosmwasm_std::Addr;
 use cw_core::{
-    msg::{ExecuteMsg, InstantiateMsg, QueryMsg},
+    msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg},
     query::{Cw20BalanceResponse, DumpStateResponse, GetItemResponse, PauseInfoResponse},
     state::Config,
 };
@@ -21,6 +21,7 @@ fn main() {
     export_schema(&schema_for!(InstantiateMsg), &out_dir);
     export_schema(&schema_for!(ExecuteMsg), &out_dir);
     export_schema(&schema_for!(QueryMsg), &out_dir);
+    export_schema(&schema_for!(MigrateMsg), &out_dir);
 
     export_schema(&schema_for!(DumpStateResponse), &out_dir);
     export_schema(&schema_for!(PauseInfoResponse), &out_dir);
diff --git a/contracts/cw-core/src/contract.rs b/contracts/cw-core/src/contract.rs
index c4cc7758..24c01576 100644
--- a/contracts/cw-core/src/contract.rs
+++ b/contracts/cw-core/src/contract.rs
@@ -13,7 +13,9 @@ use cw_utils::{parse_reply_instantiate_data, Duration};
 use cw_core_interface::voting;
 
 use crate::error::ContractError;
-use crate::msg::{ExecuteMsg, InitialItemInfo, InstantiateMsg, ModuleInstantiateInfo, QueryMsg};
+use crate::msg::{
+    ExecuteMsg, InitialItemInfo, InstantiateMsg, MigrateMsg, ModuleInstantiateInfo, QueryMsg,
+};
 use crate::query::{Cw20BalanceResponse, DumpStateResponse, GetItemResponse, PauseInfoResponse};
 use crate::state::{
     Config, ADMIN, CONFIG, CW20_LIST, CW721_LIST, ITEMS, PAUSED, PENDING_ITEM_INSTANTIATION_NAMES,
@@ -701,6 +703,12 @@ pub fn query_cw20_balances(
     to_binary(&balances)
 }
 
+#[cfg_attr(not(feature = "library"), entry_point)]
+pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
+    // Don't do any state migrations.
+    Ok(Response::default())
+}
+
 #[cfg_attr(not(feature = "library"), entry_point)]
 pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
     match msg.id {
diff --git a/contracts/cw-core/src/msg.rs b/contracts/cw-core/src/msg.rs
index d35bcaa3..089bff4f 100644
--- a/contracts/cw-core/src/msg.rs
+++ b/contracts/cw-core/src/msg.rs
@@ -188,3 +188,6 @@ pub enum QueryMsg {
     /// Gets the contract's voting module. Returns Addr.
     VotingModule {},
 }
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
+pub struct MigrateMsg {}
diff --git a/contracts/cw-proposal-single/examples/schema.rs b/contracts/cw-proposal-single/examples/schema.rs
index edeb0916..b065a931 100644
--- a/contracts/cw-proposal-single/examples/schema.rs
+++ b/contracts/cw-proposal-single/examples/schema.rs
@@ -5,7 +5,7 @@ use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, s
 use cosmwasm_std::Addr;
 use cw_core_interface::voting::InfoResponse;
 use cw_proposal_single::{
-    msg::{ExecuteMsg, InstantiateMsg, QueryMsg},
+    msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg},
     query::{ProposalListResponse, ProposalResponse, VoteListResponse, VoteResponse},
     state::Config,
 };
@@ -20,6 +20,7 @@ fn main() {
     export_schema(&schema_for!(InstantiateMsg), &out_dir);
     export_schema(&schema_for!(ExecuteMsg), &out_dir);
     export_schema(&schema_for!(QueryMsg), &out_dir);
+    export_schema(&schema_for!(MigrateMsg), &out_dir);
 
     export_schema(&schema_for!(InfoResponse), &out_dir);
     export_schema(&schema_for!(ProposalResponse), &out_dir);
diff --git a/contracts/cw-proposal-single/src/contract.rs b/contracts/cw-proposal-single/src/contract.rs
index 43193b10..0a4356b1 100644
--- a/contracts/cw-proposal-single/src/contract.rs
+++ b/contracts/cw-proposal-single/src/contract.rs
@@ -16,7 +16,7 @@ use voting::{Status, Threshold, Vote, Votes};
 
 use crate::{
     error::ContractError,
-    msg::{DepositInfo, ExecuteMsg, InstantiateMsg, QueryMsg},
+    msg::{DepositInfo, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg},
     proposal::{advance_proposal_id, Proposal},
     query::ProposalListResponse,
     query::{ProposalResponse, VoteInfo, VoteListResponse, VoteResponse},
@@ -684,6 +684,12 @@ pub fn query_info(deps: Deps) -> StdResult<Binary> {
     to_binary(&cw_core_interface::voting::InfoResponse { info })
 }
 
+#[cfg_attr(not(feature = "library"), entry_point)]
+pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
+    // Don't do any state migrations.
+    Ok(Response::default())
+}
+
 #[cfg_attr(not(feature = "library"), entry_point)]
 pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
     if msg.id % 2 == 0 {
diff --git a/contracts/cw-proposal-single/src/msg.rs b/contracts/cw-proposal-single/src/msg.rs
index 65399160..23d38328 100644
--- a/contracts/cw-proposal-single/src/msg.rs
+++ b/contracts/cw-proposal-single/src/msg.rs
@@ -161,3 +161,6 @@ pub enum QueryMsg {
     ProposalHooks {},
     VoteHooks {},
 }
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
+pub struct MigrateMsg {}
diff --git a/contracts/cw20-staked-balance-voting/examples/schema.rs b/contracts/cw20-staked-balance-voting/examples/schema.rs
index abbd82d6..c9527627 100644
--- a/contracts/cw20-staked-balance-voting/examples/schema.rs
+++ b/contracts/cw20-staked-balance-voting/examples/schema.rs
@@ -3,7 +3,7 @@ use std::fs::create_dir_all;
 
 use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for};
 use cosmwasm_std::Addr;
-use cw20_staked_balance_voting::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
+use cw20_staked_balance_voting::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
 use cw_core_interface::voting::{
     InfoResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse,
 };
@@ -17,6 +17,7 @@ fn main() {
     export_schema(&schema_for!(InstantiateMsg), &out_dir);
     export_schema(&schema_for!(ExecuteMsg), &out_dir);
     export_schema(&schema_for!(QueryMsg), &out_dir);
+    export_schema(&schema_for!(MigrateMsg), &out_dir);
 
     export_schema(&schema_for!(InfoResponse), &out_dir);
     export_schema(&schema_for!(TotalPowerAtHeightResponse), &out_dir);
diff --git a/contracts/cw20-staked-balance-voting/src/contract.rs b/contracts/cw20-staked-balance-voting/src/contract.rs
index 0a293c3e..1bbfa518 100644
--- a/contracts/cw20-staked-balance-voting/src/contract.rs
+++ b/contracts/cw20-staked-balance-voting/src/contract.rs
@@ -12,8 +12,8 @@ use std::convert::TryInto;
 
 use crate::error::ContractError;
 use crate::msg::{
-    ActiveThreshold, ActiveThresholdResponse, ExecuteMsg, InstantiateMsg, QueryMsg, StakingInfo,
-    TokenInfo,
+    ActiveThreshold, ActiveThresholdResponse, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg,
+    StakingInfo, TokenInfo,
 };
 use crate::state::{
     ACTIVE_THRESHOLD, DAO, STAKING_CONTRACT, STAKING_CONTRACT_CODE_ID,
@@ -341,6 +341,12 @@ pub fn query_active_threshold(deps: Deps) -> StdResult<Binary> {
     })
 }
 
+#[cfg_attr(not(feature = "library"), entry_point)]
+pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
+    // Don't do any state migrations.
+    Ok(Response::default())
+}
+
 #[cfg_attr(not(feature = "library"), entry_point)]
 pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result<Response, ContractError> {
     match msg.id {
diff --git a/contracts/cw20-staked-balance-voting/src/msg.rs b/contracts/cw20-staked-balance-voting/src/msg.rs
index 15befc80..00436196 100644
--- a/contracts/cw20-staked-balance-voting/src/msg.rs
+++ b/contracts/cw20-staked-balance-voting/src/msg.rs
@@ -78,3 +78,6 @@ pub enum QueryMsg {
 pub struct ActiveThresholdResponse {
     pub active_threshold: Option<ActiveThreshold>,
 }
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
+pub struct MigrateMsg {}
diff --git a/contracts/cw4-voting/examples/schema.rs b/contracts/cw4-voting/examples/schema.rs
index f77e9774..094b97d0 100644
--- a/contracts/cw4-voting/examples/schema.rs
+++ b/contracts/cw4-voting/examples/schema.rs
@@ -4,7 +4,7 @@ use std::fs::create_dir_all;
 use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for};
 use cosmwasm_std::Addr;
 use cw4::MemberDiff;
-use cw4_voting::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
+use cw4_voting::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
 use cw_core_interface::voting::{
     InfoResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse,
 };
@@ -18,6 +18,7 @@ fn main() {
     export_schema(&schema_for!(InstantiateMsg), &out_dir);
     export_schema(&schema_for!(ExecuteMsg), &out_dir);
     export_schema(&schema_for!(QueryMsg), &out_dir);
+    export_schema(&schema_for!(MigrateMsg), &out_dir);
 
     export_schema(&schema_for!(MemberDiff), &out_dir);
 
diff --git a/contracts/cw4-voting/src/contract.rs b/contracts/cw4-voting/src/contract.rs
index d6cffca5..c28a4e39 100644
--- a/contracts/cw4-voting/src/contract.rs
+++ b/contracts/cw4-voting/src/contract.rs
@@ -8,7 +8,7 @@ use cw2::set_contract_version;
 use cw_utils::parse_reply_instantiate_data;
 
 use crate::error::ContractError;
-use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
+use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
 use crate::state::{DAO_ADDRESS, GROUP_CONTRACT, TOTAL_WEIGHT, USER_WEIGHTS};
 
 const CONTRACT_NAME: &str = "crates.io:cw4-voting";
@@ -162,6 +162,12 @@ pub fn query_info(deps: Deps) -> StdResult<Binary> {
     to_binary(&cw_core_interface::voting::InfoResponse { info })
 }
 
+#[cfg_attr(not(feature = "library"), entry_point)]
+pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
+    // Don't do any state migrations.
+    Ok(Response::default())
+}
+
 #[cfg_attr(not(feature = "library"), entry_point)]
 pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result<Response, ContractError> {
     match msg.id {
diff --git a/contracts/cw4-voting/src/msg.rs b/contracts/cw4-voting/src/msg.rs
index 0e65a202..fb98540d 100644
--- a/contracts/cw4-voting/src/msg.rs
+++ b/contracts/cw4-voting/src/msg.rs
@@ -21,3 +21,6 @@ pub enum QueryMsg {
     GroupContract {},
     Dao {},
 }
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
+pub struct MigrateMsg {}


Test patch:
diff --git a/contracts/cw-core/src/tests.rs b/contracts/cw-core/src/tests.rs
index 70db7019..4da879a0 100644
--- a/contracts/cw-core/src/tests.rs
+++ b/contracts/cw-core/src/tests.rs
@@ -1,4 +1,4 @@
-use cosmwasm_std::{to_binary, Addr, Empty, Uint128, WasmMsg};
+use cosmwasm_std::{to_binary, Addr, CosmosMsg, Empty, Uint128, WasmMsg};
 use cw2::ContractVersion;
 use cw_core_interface::voting::VotingPowerAtHeightResponse;
 use cw_multi_test::{App, Contract, ContractWrapper, Executor};
@@ -6,8 +6,8 @@ use cw_utils::{Duration, Expiration};
 
 use crate::{
     msg::{
-        Admin, ExecuteMsg, InitialItem, InitialItemInfo, InstantiateMsg, ModuleInstantiateInfo,
-        QueryMsg,
+        Admin, ExecuteMsg, InitialItem, InitialItemInfo, InstantiateMsg, MigrateMsg,
+        ModuleInstantiateInfo, QueryMsg,
     },
     query::{Cw20BalanceResponse, DumpStateResponse, GetItemResponse, PauseInfoResponse},
     state::Config,
@@ -59,7 +59,8 @@ fn cw_core_contract() -> Box<dyn Contract<Empty>> {
         crate::contract::instantiate,
         crate::contract::query,
     )
-    .with_reply(crate::contract::reply);
+    .with_reply(crate::contract::reply)
+    .with_migrate(crate::contract::migrate);
     Box::new(contract)
 }
 
@@ -1840,3 +1841,86 @@ fn test_pause() {
         }
     );
 }
+
+#[test]
+fn test_migrate() {
+    let mut app = App::default();
+    let govmod_id = app.store_code(sudo_proposal_contract());
+    let voting_id = app.store_code(cw20_balances_voting());
+    let gov_id = app.store_code(cw_core_contract());
+    let cw20_id = app.store_code(cw20_contract());
+
+    let govmod_instantiate = cw_proposal_sudo::msg::InstantiateMsg {
+        root: CREATOR_ADDR.to_string(),
+    };
+    let voting_instantiate = cw20_balance_voting::msg::InstantiateMsg {
+        token_info: cw20_balance_voting::msg::TokenInfo::New {
+            code_id: cw20_id,
+            label: "DAO DAO voting".to_string(),
+            name: "DAO DAO".to_string(),
+            symbol: "DAO".to_string(),
+            decimals: 6,
+            initial_balances: vec![cw20::Cw20Coin {
+                address: CREATOR_ADDR.to_string(),
+                amount: Uint128::from(2u64),
+            }],
+            marketing: None,
+        },
+    };
+
+    // Instantiate the core module with an admin to do migrations.
+    let gov_instantiate = InstantiateMsg {
+        admin: None,
+        name: "DAO DAO".to_string(),
+        description: "A DAO that builds DAOs.".to_string(),
+        image_url: None,
+        automatically_add_cw20s: false,
+        automatically_add_cw721s: false,
+        voting_module_instantiate_info: ModuleInstantiateInfo {
+            code_id: voting_id,
+            msg: to_binary(&voting_instantiate).unwrap(),
+            admin: Admin::CoreContract {},
+            label: "voting module".to_string(),
+        },
+        proposal_modules_instantiate_info: vec![ModuleInstantiateInfo {
+            code_id: govmod_id,
+            msg: to_binary(&govmod_instantiate).unwrap(),
+            admin: Admin::CoreContract {},
+            label: "governance module".to_string(),
+        }],
+        initial_items: None,
+    };
+
+    let core_addr = app
+        .instantiate_contract(
+            gov_id,
+            Addr::unchecked(CREATOR_ADDR),
+            &gov_instantiate,
+            &[],
+            "cw-governance",
+            Some(CREATOR_ADDR.to_string()),
+        )
+        .unwrap();
+
+    let state: DumpStateResponse = app
+        .wrap()
+        .query_wasm_smart(core_addr.clone(), &QueryMsg::DumpState {})
+        .unwrap();
+
+    app.execute(
+        Addr::unchecked(CREATOR_ADDR),
+        CosmosMsg::Wasm(WasmMsg::Migrate {
+            contract_addr: core_addr.to_string(),
+            new_code_id: gov_id,
+            msg: to_binary(&MigrateMsg {}).unwrap(),
+        }),
+    )
+    .unwrap();
+
+    let new_state: DumpStateResponse = app
+        .wrap()
+        .query_wasm_smart(core_addr, &QueryMsg::DumpState {})
+        .unwrap();
+
+    assert_eq!(new_state, state);
+}
diff --git a/contracts/cw-proposal-single/src/tests.rs b/contracts/cw-proposal-single/src/tests.rs
index ea6a86ed..8ebad115 100644
--- a/contracts/cw-proposal-single/src/tests.rs
+++ b/contracts/cw-proposal-single/src/tests.rs
@@ -1,6 +1,6 @@
 use std::u128;
 
-use cosmwasm_std::{to_binary, Addr, Decimal, Empty, Uint128};
+use cosmwasm_std::{to_binary, Addr, CosmosMsg, Decimal, Empty, Uint128, WasmMsg};
 use cw20::Cw20Coin;
 use cw20_staked_balance_voting::msg::ActiveThreshold;
 use cw_multi_test::{next_block, App, Contract, ContractWrapper, Executor};
@@ -14,7 +14,7 @@ use testing::{ShouldExecute, TestVote};
 use voting::{PercentageThreshold, Status, Threshold, Vote, Votes};
 
 use crate::{
-    msg::{DepositInfo, DepositToken, ExecuteMsg, InstantiateMsg, QueryMsg},
+    msg::{DepositInfo, DepositToken, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg},
     proposal::Proposal,
     query::{ProposalListResponse, ProposalResponse, VoteInfo, VoteResponse},
     state::{CheckedDepositInfo, Config},
@@ -47,7 +47,8 @@ fn single_proposal_contract() -> Box<dyn Contract<Empty>> {
         crate::contract::instantiate,
         crate::contract::query,
     )
-    .with_reply(crate::contract::reply);
+    .with_reply(crate::contract::reply)
+    .with_migrate(crate::contract::migrate);
     Box::new(contract)
 }
 
@@ -3262,3 +3263,59 @@ fn test_large_absolute_count_threshold() {
         None,
     );
 }
+
+#[test]
+fn test_migrate() {
+    let mut app = App::default();
+    let govmod_id = app.store_code(single_proposal_contract());
+
+    let threshold = Threshold::AbsolutePercentage {
+        percentage: PercentageThreshold::Majority {},
+    };
+    let max_voting_period = cw_utils::Duration::Height(6);
+    let instantiate = InstantiateMsg {
+        threshold,
+        max_voting_period,
+        only_members_execute: false,
+        allow_revoting: false,
+        deposit_info: None,
+    };
+
+    let governance_addr =
+        instantiate_with_cw20_balances_governance(&mut app, govmod_id, instantiate, None);
+    let governance_modules: Vec<Addr> = app
+        .wrap()
+        .query_wasm_smart(
+            governance_addr.clone(),
+            &cw_core::msg::QueryMsg::ProposalModules {
+                start_at: None,
+                limit: None,
+            },
+        )
+        .unwrap();
+
+    assert_eq!(governance_modules.len(), 1);
+    let govmod_single = governance_modules.into_iter().next().unwrap();
+
+    let config: Config = app
+        .wrap()
+        .query_wasm_smart(govmod_single.clone(), &QueryMsg::Config {})
+        .unwrap();
+
+    app.execute(
+        governance_addr,
+        CosmosMsg::Wasm(WasmMsg::Migrate {
+            contract_addr: govmod_single.to_string(),
+            new_code_id: govmod_id,
+            msg: to_binary(&MigrateMsg {}).unwrap(),
+        }),
+    )
+    .unwrap();
+
+    let new_config: Config = app
+        .wrap()
+        .query_wasm_smart(govmod_single, &QueryMsg::Config {})
+        .unwrap();
+
+    assert_eq!(config, new_config);
+}
diff --git a/contracts/cw20-staked-balance-voting/src/tests.rs b/contracts/cw20-staked-balance-voting/src/tests.rs
index 3575ed61..c5fd34db 100644
--- a/contracts/cw20-staked-balance-voting/src/tests.rs
+++ b/contracts/cw20-staked-balance-voting/src/tests.rs
@@ -1,11 +1,12 @@
-use cosmwasm_std::{to_binary, Addr, Decimal, Empty, Uint128};
+use cosmwasm_std::{to_binary, Addr, CosmosMsg, Decimal, Empty, Uint128, WasmMsg};
 use cw2::ContractVersion;
 use cw20::{BalanceResponse, Cw20Coin, MinterResponse, TokenInfoResponse};
 use cw_core_interface::voting::{InfoResponse, IsActiveResponse, VotingPowerAtHeightResponse};
 use cw_multi_test::{next_block, App, Contract, ContractWrapper, Executor};
 
 use crate::msg::{
-    ActiveThreshold, ActiveThresholdResponse, ExecuteMsg, InstantiateMsg, QueryMsg, StakingInfo,
+    ActiveThreshold, ActiveThresholdResponse, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg,
+    StakingInfo,
 };
 
 const DAO_ADDR: &str = "dao";
@@ -35,7 +36,8 @@ fn staked_balance_voting_contract() -> Box<dyn Contract<Empty>> {
         crate::contract::instantiate,
         crate::contract::query,
     )
-    .with_reply(crate::contract::reply);
+    .with_reply(crate::contract::reply)
+    .with_migrate(crate::contract::migrate);
     Box::new(contract)
 }
 
@@ -1251,3 +1253,66 @@ fn test_active_threshold_absolute_count_invalid() {
         },
     );
 }
+
+#[test]
+fn test_migrate() {
+    let mut app = App::default();
+    let cw20_id = app.store_code(cw20_contract());
+    let voting_id = app.store_code(staked_balance_voting_contract());
+    let staking_contract_id = app.store_code(staking_contract());
+
+    let voting_addr = app
+        .instantiate_contract(
+            voting_id,
+            Addr::unchecked(DAO_ADDR),
+            &InstantiateMsg {
+                token_info: crate::msg::TokenInfo::New {
+                    code_id: cw20_id,
+                    label: "DAO DAO voting".to_string(),
+                    name: "DAO DAO".to_string(),
+                    symbol: "DAO".to_string(),
+                    decimals: 6,
+                    initial_balances: vec![Cw20Coin {
+                        address: CREATOR_ADDR.to_string(),
+                        amount: Uint128::from(2u64),
+                    }],
+                    marketing: None,
+                    unstaking_duration: None,
+                    staking_code_id: staking_contract_id,
+                    initial_dao_balance: Some(Uint128::zero()),
+                },
+                active_threshold: None,
+            },
+            &[],
+            "voting module",
+            Some(DAO_ADDR.to_string()),
+        )
+        .unwrap();
+
+    let info: InfoResponse = app
+        .wrap()
+        .query_wasm_smart(voting_addr.clone(), &QueryMsg::Info {})
+        .unwrap();
+
+    let dao: Addr = app
+        .wrap()
+        .query_wasm_smart(voting_addr.clone(), &QueryMsg::Dao {})
+        .unwrap();
+
+    app.execute(
+        dao,
+        CosmosMsg::Wasm(WasmMsg::Migrate {
+            contract_addr: voting_addr.to_string(),
+            new_code_id: voting_id,
+            msg: to_binary(&MigrateMsg {}).unwrap(),
+        }),
+    )
+    .unwrap();
+
+    let new_info: InfoResponse = app
+        .wrap()
+        .query_wasm_smart(voting_addr, &QueryMsg::Info {})
+        .unwrap();
+
+    assert_eq!(info, new_info);
+}
diff --git a/contracts/cw4-voting/src/tests.rs b/contracts/cw4-voting/src/tests.rs
index 0097d0ca..4a3d6541 100644
--- a/contracts/cw4-voting/src/tests.rs
+++ b/contracts/cw4-voting/src/tests.rs
@@ -1,4 +1,4 @@
-use cosmwasm_std::{Addr, Empty, Uint128};
+use cosmwasm_std::{to_binary, Addr, CosmosMsg, Empty, Uint128, WasmMsg};
 use cw2::ContractVersion;
 use cw_core_interface::voting::{
     InfoResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse,
@@ -6,7 +6,7 @@ use cw_core_interface::voting::{
 use cw_multi_test::{next_block, App, Contract, ContractWrapper, Executor};
 
 use crate::{
-    msg::{ExecuteMsg, InstantiateMsg, QueryMsg},
+    msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg},
     ContractError,
 };
 
@@ -30,7 +30,8 @@ fn voting_contract() -> Box<dyn Contract<Empty>> {
         crate::contract::instantiate,
         crate::contract::query,
     )
-    .with_reply(crate::contract::reply);
+    .with_reply(crate::contract::reply)
+    .with_migrate(crate::contract::migrate);
     Box::new(contract)
 }
 
@@ -468,3 +469,75 @@ fn test_power_at_height() {
     assert_eq!(total_voting_power.power, Uint128::new(2u128));
     assert_eq!(total_voting_power.height, app.block_info().height - 1);
 }
+
+#[test]
+fn test_migrate() {
+    let mut app = App::default();
+
+    let initial_members = vec![
+        cw4::Member {
+            addr: ADDR1.to_string(),
+            weight: 1,
+        },
+        cw4::Member {
+            addr: ADDR2.to_string(),
+            weight: 1,
+        },
+        cw4::Member {
+            addr: ADDR3.to_string(),
+            weight: 1,
+        },
+    ];
+
+    // Instantiate with no members, error
+    let voting_id = app.store_code(voting_contract());
+    let cw4_id = app.store_code(cw4_contract());
+    let msg = InstantiateMsg {
+        cw4_group_code_id: cw4_id,
+        initial_members,
+    };
+    let voting_addr = app
+        .instantiate_contract(
+            voting_id,
+            Addr::unchecked(DAO_ADDR),
+            &msg,
+            &[],
+            "voting module",
+            Some(DAO_ADDR.to_string()),
+        )
+        .unwrap();
+
+    let power: VotingPowerAtHeightResponse = app
+        .wrap()
+        .query_wasm_smart(
+            voting_addr.clone(),
+            &QueryMsg::VotingPowerAtHeight {
+                address: ADDR1.to_string(),
+                height: None,
+            },
+        )
+        .unwrap();
+
+    app.execute(
+        Addr::unchecked(DAO_ADDR),
+        CosmosMsg::Wasm(WasmMsg::Migrate {
+            contract_addr: voting_addr.to_string(),
+            new_code_id: voting_id,
+            msg: to_binary(&MigrateMsg {}).unwrap(),
+        }),
+    )
+    .unwrap();
+
+    let new_power: VotingPowerAtHeightResponse = app
+        .wrap()
+        .query_wasm_smart(
+            voting_addr,
+            &QueryMsg::VotingPowerAtHeight {
+                address: ADDR1.to_string(),
+                height: None,
+            },
+        )
+        .unwrap();
+
+    assert_eq!(new_power, power)
+}