Repository: DA0-DA0/dao-contracts
Pull request #648 (2023-02-12 01:39:09)

Integration test to measure max NFTs mintable in a single proposal.
based on tuning on my local setup, the current number is 55.

codecov-commenter: # [Codecov](https://codecov.io/gh/DA0-DA0/dao-contracts/pull/648?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0) Report
Base: **92.64**% // Head: **92.64**% // No change to project coverage :thumbsup:
> Coverage data is based on head [(`523e594`)](https://codecov.io/gh/DA0-DA0/dao-contracts/pull/648?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0) compared to base [(`cfa8444`)](https://codecov.io/gh/DA0-DA0/dao-contracts/commit/cfa8444501c96b8c146e4ab3d80aa9d8689dcfbd?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0).
> Patch has no changes to coverable lines.

:mega: This organization is not using Codecov’s [GitHub App Integration](https://github.com/apps/codecov). We recommend you install it so Codecov can continue to function properly for your repositories. [Learn more](https://about.codecov.io/blog/codecov-is-updating-its-github-integration/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0)

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main     #648   +/-   ##
=======================================
  Coverage   92.64%   92.64%           
=======================================
  Files          58       58           
  Lines        5005     5005           
=======================================
  Hits         4637     4637           
  Misses        368      368           
```



Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/DA0-DA0/dao-contracts/pull/648?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0).

github-actions[bot]: <h1>Cosm-Orc Gas Usage</h1> 
 
 | Contract | Op Name | Gas Used | Old Gas Used | Gas Diff | File | 
 | --- | --- | --- | --- | --- | --- | 
| cw721_base | Instantiate__instantiate_cw721_base | 166240 | 164837 | +0.8511% | ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs:22 |
 
 <details><summary>Raw Report for 523e5941e5d4ea08917a5ab5e8efb5be50b0cf3c </summary><br/> 

| Contract | Op Name | Gas Used | Gas Wanted | File | 
 | --- | --- | --- | --- | --- |
| dao_core | Instantiate__inst_dao_no_admin | 1275335 | 1890198 | ci/integration-tests/src/helpers/helper.rs:101 |
| dao_core | Instantiate__exc_stake_create_dao | 1275311 | 1890162 | ci/integration-tests/src/helpers/helper.rs:101 |
| dao_core | Execute__exc_items_rm | 192820 | 266513 | ci/integration-tests/src/tests/cw_core_test.rs:171 |
| dao_core | Execute__exc_items_set | 194487 | 269013 | ci/integration-tests/src/tests/cw_core_test.rs:136 |
| dao_core | Instantiate__exc_items_create_dao | 1276385 | 1891773 | ci/integration-tests/src/helpers/helper.rs:101 |
| dao_core | Execute__exc_admin_msgs_pause_dao | 194613 | 269202 | ci/integration-tests/src/tests/cw_core_test.rs:76 |
| dao_core | Instantiate__exc_admin_msgs_create_dao | 1275335 | 1890198 | ci/integration-tests/src/helpers/helper.rs:101 |
| dao_core | Instantiate__exc_admin_msgs_create_dao_with_admin | 1276385 | 1891773 | ci/integration-tests/src/helpers/helper.rs:101 |
| dao_core | Instantiate__inst_admin_create_dao | 1276385 | 1891773 | ci/integration-tests/src/helpers/helper.rs:101 |
| dao_core | Instantiate__create_dao | 1275335 | 1890198 | ci/integration-tests/src/helpers/helper.rs:101 |
| dao_core | Store__Store | 6171396 | 9234305 | ci/integration-tests/src/helpers/chain.rs:97 |
| cw20_base | Execute__exc_stake_stake_tokens | 234535 | 320952 | ci/integration-tests/src/tests/cw20_stake_test.rs:76 |
| cw20_base | Execute__cw20_base_increase_allowance | 142201 | 190586 | ci/integration-tests/src/helpers/helper.rs:216 |
| cw20_base | Execute__send_and_stake_cw20 | 234267 | 328683 | ci/integration-tests/src/helpers/helper.rs:180 |
| cw20_base | Store__Store | 4180849 | 6248484 | ci/integration-tests/src/helpers/chain.rs:97 |
| cw721_base | Instantiate__instantiate_cw721_base | 166240 | 226643 | ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs:22 |
| cw721_base | Execute__mint_nft | 140542 | 188097 | ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs:93 |
| cw721_base | Execute__stake_nft | 234591 | 329168 | ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs:76 |
| cw721_base | Store__Store | 3369402 | 5031314 | ci/integration-tests/src/helpers/chain.rs:97 |
| dao_voting_cw721_staked | Execute__claim_nfts | 5710869 | 8543393 | ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs:126 |
| dao_voting_cw721_staked | Instantiate__instantiate_dao_voting_cw721_staked | 178255 | 244667 | ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs:49 |
| dao_voting_cw721_staked | Execute__unstake_nfts | 230319 | 322760 | ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs:111 |
| dao_voting_cw721_staked | Store__Store | 3349655 | 5001693 | ci/integration-tests/src/helpers/chain.rs:97 |
| multiple_contracts | Execute__batch_cw721_stake_max_claims | 4196444 | 6254241 | ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs:257 |
| dao_pre_propose_single | Execute__pre_propose_propose | 1806420 | 2686818 | ci/integration-tests/src/helpers/helper.rs:235 |
| dao_pre_propose_single | Store__Store | 4171086 | 6233840 | ci/integration-tests/src/helpers/chain.rs:97 |
| dao_proposal_single | Execute__dao_proposal_single_vote | 5982531 | 8950832 | ci/integration-tests/src/helpers/helper.rs:285 |
| dao_proposal_single | Store__Store | 6678773 | 9995370 | ci/integration-tests/src/helpers/chain.rs:97 |
| cw20_stake | Store__Store | 3936332 | 5881709 | ci/integration-tests/src/helpers/chain.rs:97 |
| cw20_stake_external_rewards | Store__Store | 3349837 | 5001966 | ci/integration-tests/src/helpers/chain.rs:97 |
| cw20_stake_reward_distributor | Store__Store | 2834062 | 4228304 | ci/integration-tests/src/helpers/chain.rs:97 |
| cw4_group | Store__Store | 2783570 | 4152566 | ci/integration-tests/src/helpers/chain.rs:97 |
| cw_admin_factory | Store__Store | 2019807 | 3006921 | ci/integration-tests/src/helpers/chain.rs:97 |
| cw_fund_distributor | Store__Store | 3555146 | 5309930 | ci/integration-tests/src/helpers/chain.rs:97 |
| cw_payroll_factory | Store__Store | 3729424 | 5571347 | ci/integration-tests/src/helpers/chain.rs:97 |
| cw_token_swap | Store__Store | 2382910 | 3551576 | ci/integration-tests/src/helpers/chain.rs:97 |
| cw_vesting | Store__Store | 3583668 | 5352713 | ci/integration-tests/src/helpers/chain.rs:97 |
| dao_migrator | Store__Store | 5022872 | 7511519 | ci/integration-tests/src/helpers/chain.rs:97 |
| dao_pre_propose_approval_single | Store__Store | 4918820 | 7355441 | ci/integration-tests/src/helpers/chain.rs:97 |
| dao_pre_propose_approver | Store__Store | 3677489 | 5493444 | ci/integration-tests/src/helpers/chain.rs:97 |
| dao_pre_propose_multiple | Store__Store | 4249450 | 6351386 | ci/integration-tests/src/helpers/chain.rs:97 |
| dao_proposal_condorcet | Store__Store | 4631624 | 6924647 | ci/integration-tests/src/helpers/chain.rs:97 |
| dao_proposal_multiple | Store__Store | 6476727 | 9692301 | ci/integration-tests/src/helpers/chain.rs:97 |
| dao_voting_cw20_staked | Store__Store | 3626321 | 5416692 | ci/integration-tests/src/helpers/chain.rs:97 |
| dao_voting_cw4 | Store__Store | 2687487 | 4008441 | ci/integration-tests/src/helpers/chain.rs:97 |
| dao_voting_native_staked | Store__Store | 2977208 | 4443018 | ci/integration-tests/src/helpers/chain.rs:97 |
</details>

Patch:
diff --git a/README.md b/README.md
index bcb99b7c..c02bd21b 100644
--- a/README.md
+++ b/README.md
@@ -1,64 +1,34 @@
-# DAO DAO Contracts
+# DAO Contracts
 
 [![codecov](https://codecov.io/gh/DA0-DA0/dao-contracts/branch/main/graph/badge.svg?token=SCKOIPYZPV)](https://codecov.io/gh/DA0-DA0/dao-contracts)
 
-This is a collection of smart contracts for building composable,
-modular, and upgradable DAOs.
-
-For an overview of our contract design, see [our
-wiki](https://github.com/DA0-DA0/dao-contracts/wiki/DAO-DAO-Contracts-Design).
-
-| Audited contracts (tag: v1.0.0)                                                | Description                                            |
-| :----------------------------------------------------------------------------- | :----------------------------------------------------- |
-| [dao-core](contracts/dao-core)                                                 | The core module for a DAO DAO DAO.                     |
-| [dao-proposal-single](contracts/proposal/dao-proposal-single)                  | A proposal module for single choice (yes / no) voting. |
-| [dao-voting-cw20-staked](contracts/voting/dao-voting-cw20-staked)              | A voting power module for staked governance tokens.    |
-| [dao-voting-cw4](contracts/voting/dao-voting-cw4)                              | A voting power module for multisig-style voting.       |
-| [cw20-stake](contracts/staking/cw20-stake)                                     | A contract for staking cw20 tokens.                    |
-| [cw20-stake-external-rewards](contracts/staking/cw20-stake-external-rewards)   | A contract for providing external staking rewards.     |
-| [cw20-stake-reward-distributor](contracts/staking/cw20-stake-reward-distributor) | A contract for distributing rewards via stake-cw20.    |
-
-| Unaudited contracts                                                                      | Description                                                                            |
-| :--------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------- |
-| [dao-proposal-multiple](contracts/proposal/dao-proposal-multiple)                        | A proposal module for multiple choice proposals.                                       |
-| [dao-voting-cw721-staked](contracts/voting/dao-voting-cw721-staked)                      | A voting module based on staked NFTs                                                   |
-| [dao-pre-propose-single](contracts/pre-propose/dao-pre-propose-single)                   | A pre-propose module for single choice proposals.                                      |
-| [dao-voting-native-staked](contracts/proposal/dao-voting-native-staked)                  | A voting power based on staked native tokens not used to secure the chain e.g. ION.    |
-| [dao-voting-staking-denom-staked](contracts/proposal/dao-voting-staking-denom-staked)    | A voting power module based on staked native tokens used to secure the chain e.g. JUNO |
-| [dao-pre-propose-multiple](contracts/pre-propose/dao-pre-propose-multiple)               | A pre-propose module for multiple choice proposals.                                    |
-| [dao-pre-propose-approval-single](contracts/pre-propose/dao-pre-propose-approval-single) | A pre-propose module for implementing an approval flow.                                |
-| [dao-pre-propose-approver](contracts/pre-propose/dao-pre-propose-approver)               | A pre-propose module for automatically creating proposals that need to be approved.    |
-| [cw-token-swap](contracts/external/cw-token-swap)                                        | An escrow contract for swapping tokens between DAOs.                                   |
-| [cw-vesting](contracts/external/cw-vesting)                                              | A vesting payment contract.                                                            |
-| [cw-payroll-factory](contracts/external/cw-payroll-factory)                              | A factory contract for `cw-vesting`.                                                   |
-
-Audited contracts have completed audits by
-[securityDAO](https://github.com/securityDAO/audits/blob/7bb8e4910baaea89fddfc025591658f44adbc27c/cosmwasm/dao-contracts/v0.3%20DAO%20DAO%20audit.pdf)
-and [Oak
-Security](https://github.com/oak-security/audit-reports/blob/2377ba8cfcfd505283c789d706311b06771d6db4/DAO%20DAO/2022-06-22%20Audit%20Report%20-%20DAO%20DAO%20v1.0.pdf)
-on the `v1.0.0` tag. An audit for the v2 contracts is forthcoming.
-
-## Packages
-
-| Package                                               | Description                                                                               |
-| :---------------------------------------------------- | :---------------------------------------------------------------------------------------- |
-| [cw721-controllers](packages/cw721-controllers)       | Manages claims for the [cw721 staking contract](contracts/voting/dao-voting-cw721-staked) |
-| [cw-hooks](packages/cw-hooks)                         | Shared hooks functionality.                                                               |
-| [dao-interface](packages/dao-interface)               | Provides types and interfaces for interacting with DAO modules.                           |
-| [dao-macros](packages/dao-macros)                     | A collection of macros to derive DAO module interfaces on message enums.                  |
-| [dao-pre-propose-base](packages/dao-pre-propose-base) | Base package used to implement pre-propose modules.                                       |
-| [dao-proposal-hooks](packages/dao-proposal-hooks)     | Interface for managing and dispatching hooks from a proposal module.                      |
-| [dao-testing](packages/dao-testing)                   | Common testing functions and types for DAO modules.                                       |
-| [dao-vote-hooks](packages/dao-vote-hooks)             | Interface for managing and dispatching vote hooks.                                        |
-| [dao-voting](packages/dao-voting)                     | Types and associated methods for handling voting in a CosmWasm DAO.                       |
-| [cw-denom](packages/cw-denom)                         | Utilities for working with cw20 and native denoms.                                        |
-| [cw-paginate](packages/cw-paginate)                   | Convenience methods for paginating keys and values in a CosmWasm `Map` or `SnapshotMap`.  |
-
-Packages have completed audits by
-[securityDAO](https://github.com/securityDAO/audits/blob/7bb8e4910baaea89fddfc025591658f44adbc27c/cosmwasm/dao-contracts/v0.3%20DAO%20DAO%20audit.pdf)
-and [Oak
-Security](https://github.com/oak-security/audit-reports/blob/2377ba8cfcfd505283c789d706311b06771d6db4/DAO%20DAO/2022-06-22%20Audit%20Report%20-%20DAO%20DAO%20v1.0.pdf)
-on the `v1.0.0` tag. An audit for the v2 packages is forthcoming.
+This is a collection of smart contracts for building composable, modular, and upgradable DAOs.
+
+For a detailed look at how these contracts work, see [our wiki](https://github.com/DA0-DA0/dao-contracts/wiki/DAO-DAO-Contracts-Design).
+
+Our most recently [audited](https://github.com/oak-security/audit-reports/blob/master/DAO%20DAO/2023-02-06%20Audit%20Report%20-%20DAO%20DAO%202%20v1.0.pdf) release is `v2.0.0`. If you believe you have found a problem, please [let us know](SECURITY.md).
+
+## Overview
+
+Every DAO is made up of three modules:
+
+1. A voting power module, which manages the voting power of DAO members.
+2. Any number of proposal modules, which manage proposals in the DAO.
+3. A core module, which holds the DAO treasury.
+
+![image](https://user-images.githubusercontent.com/30676292/220181882-737c4dd3-a85d-498c-a1f2-067b317418a9.png)
+
+For example, voting power might be based on [staked governance tokens](https://github.com/DA0-DA0/dao-contracts/tree/main/contracts/voting/dao-voting-cw20-staked), [staked NFTs](https://github.com/DA0-DA0/dao-contracts/tree/main/contracts/voting/dao-voting-cw721-staked), or [membership](https://github.com/DA0-DA0/dao-contracts/tree/main/contracts/voting/dao-voting-cw4) and proposal modules might implement [yes/no](https://github.com/DA0-DA0/dao-contracts/tree/main/contracts/proposal/dao-proposal-single), [multiple-choice](https://github.com/DA0-DA0/dao-contracts/tree/main/contracts/proposal/dao-proposal-multiple), or [ranked-choice](https://github.com/DA0-DA0/dao-contracts/tree/main/contracts/proposal/dao-proposal-condorcet) voting.
+
+Each module type has a [standard interface](https://github.com/DA0-DA0/dao-contracts/wiki/DAO-DAO-Contracts-Design). As a result, any voting module can be used with any proposal module, and any proposal module with any voting module.
+
+The best way to get started is to create a DAO! We maintain an [open source](https://github.com/DA0-DA0/dao-dao-ui) frontend you can find at [daodao.zone](https://daodao.zone).
+
+## Why?
+
+Our institutions grew rapidly after 1970, but as time passed their priorities shifted from growth, to protectionism. We're fighting this. We believe The Internet is where the organizations of tomorrow will be built.
+
+DAO DAO is a global community working on Internet governance, and [a real DAO](https://daodao.zone/dao/juno10h0hc64jv006rr8qy0zhlu4jsxct8qwa0vtaleayh0ujz0zynf2s2r7v8q#proposals). We've never raised money, and all our work is open-source. We hope you'll [join us](https://discord.gg/sAaGuyW3D2).
 
 ## Links and Resources
 
@@ -71,23 +41,19 @@ on the `v1.0.0` tag. An audit for the v2 packages is forthcoming.
 
 ## Developers
 
-Information about our development workflow and how to contribute can
-be found in [CONTRIBUTING.md](./CONTRIBUTING.md).
+Information about our development workflow and how to contribute can be found in [CONTRIBUTING.md](./CONTRIBUTING.md).
 
 ## Testing
 
 ### Unit tests
 
-Run `cargo test`, or `just test` from the project root to run the unit
-tests.
+Run `cargo test`, or `just test` from the project root to run the unit tests.
 
 ### Integration tests
 
-Run `just bootstrap-dev` to spin up a local environment and `just
-integration-test-dev` to run tests against it.
+Run `just bootstrap-dev` to spin up a local environment and `just integration-test-dev` to run tests against it.
 
-See [ci/integration-tests/README.md](ci/integration_tests/README.md)
-for more information.
+See [ci/integration-tests/README.md](ci/integration-tests/README.md) for more information.
 
 ## Disclaimer
 
diff --git a/SECURITY.md b/SECURITY.md
index 7e05249b..87f0c970 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -9,7 +9,8 @@ For Discord, please DM:
 - elsehow#3115 (PST, more avaliable than ekez in PST mornings)
 - Callum#5521 (UTC)
 
-DM as many of us as you need to get a response.
+DM as many of us as you need to get a response. You can join our
+Discord via [this link](https://discord.com/invite/sAaGuyW3D2).
 
 If you're concerned about Discord's phone number requirements feel
 free to email a report to ekez at `ekez@withoutdoing.com`. That email
diff --git a/contracts/dao-core/schema/dao-core.json b/contracts/dao-core/schema/dao-core.json
index 97ccd889..96faa71f 100644
--- a/contracts/dao-core/schema/dao-core.json
+++ b/contracts/dao-core/schema/dao-core.json
@@ -2075,7 +2075,17 @@
           "proposal_params": {
             "type": "array",
             "items": {
-              "$ref": "#/definitions/ProposalParams"
+              "type": "array",
+              "items": [
+                {
+                  "type": "string"
+                },
+                {
+                  "$ref": "#/definitions/ProposalParams"
+                }
+              ],
+              "maxItems": 2,
+              "minItems": 2
             }
           }
         },
diff --git a/contracts/dao-core/src/migrate_msg.rs b/contracts/dao-core/src/migrate_msg.rs
index 64798298..955a1ff2 100644
--- a/contracts/dao-core/src/migrate_msg.rs
+++ b/contracts/dao-core/src/migrate_msg.rs
@@ -54,7 +54,7 @@ pub struct MigrationModuleParams {
     /// migration will be aborted.
     pub migrate_stake_cw20_manager: Option<bool>,
     // dao_proposal_single
-    pub proposal_params: Vec<ProposalParams>,
+    pub proposal_params: Vec<(String, ProposalParams)>,
 }
 
 #[cw_serde]
diff --git a/contracts/external/dao-migrator/src/contract.rs b/contracts/external/dao-migrator/src/contract.rs
index b57aa441..7229a841 100644
--- a/contracts/external/dao-migrator/src/contract.rs
+++ b/contracts/external/dao-migrator/src/contract.rs
@@ -1,4 +1,4 @@
-use std::env;
+use std::{collections::HashSet, env};
 
 #[cfg(not(feature = "library"))]
 use cosmwasm_std::entry_point;
@@ -87,19 +87,34 @@ fn execute_migration_v1_v2(
         return Err(ContractError::Unauthorized {});
     }
 
+    //Check if params doesn't have duplicates
+    let mut uniq = HashSet::new();
+    if !migration_params
+        .proposal_params
+        .iter()
+        .all(|(addr, _)| uniq.insert(addr))
+    {
+        return Err(ContractError::DuplicateProposalParams);
+    }
+
     // List of code ids pairs we got and the migration msg of each one of them.
-    let proposal_pairs: Vec<CodeIdPair> = migration_params
+    let proposal_pairs: Vec<(String, CodeIdPair)> = migration_params
         .proposal_params
         .iter()
-        .map(|proposal_params| {
-            CodeIdPair::new(
-                v1_code_ids.proposal_single,
-                v2_code_ids.proposal_single,
-                MigrationMsgs::DaoProposalSingle(dao_proposal_single::msg::MigrateMsg::FromV1 {
-                    close_proposal_on_execution_failure: proposal_params
-                        .close_proposal_on_execution_failure,
-                    pre_propose_info: proposal_params.pre_propose_info.clone(),
-                }),
+        .map(|(addr, proposal_params)| {
+            (
+                addr.clone(),
+                CodeIdPair::new(
+                    v1_code_ids.proposal_single,
+                    v2_code_ids.proposal_single,
+                    MigrationMsgs::DaoProposalSingle(
+                        dao_proposal_single::msg::MigrateMsg::FromV1 {
+                            close_proposal_on_execution_failure: proposal_params
+                                .close_proposal_on_execution_failure,
+                            pre_propose_info: proposal_params.pre_propose_info.clone(),
+                        },
+                    ),
+                ),
             )
         })
         .collect(); // cw-proposal-single -> dao_proposal_single
@@ -215,20 +230,30 @@ fn execute_migration_v1_v2(
         },
     )?;
 
-    // We add 1 because migration module is added last, and we skip it.
-    if proposal_modules.len() != (proposal_pairs.len() + 1) {
+    // We remove 1 because migration module is a proposal module, and we skip it.
+    if proposal_modules.len() - 1 != (proposal_pairs.len()) {
         return Err(ContractError::MigrationParamsNotEqualProposalModulesLength);
     }
 
     // Loop over proposals and verify that they are valid DAO DAO modules
     // and set them to be migrated.
-    proposal_modules.into_iter().enumerate().try_for_each(
-        |(proposal_index, module)| -> Result<(), ContractError> {
+    proposal_modules
+        .iter()
+        .try_for_each(|module| -> Result<(), ContractError> {
             // Instead of doing 2 loops, just ignore our module, we don't care about the vec after this.
             if module.address == env.contract.address {
                 return Ok(());
             }
 
+            let proposal_pair = proposal_pairs
+                .iter()
+                .find(|(addr, _)| addr == module.address.as_str())
+                .ok_or(ContractError::ProposalModuleNotFoundInParams {
+                    addr: module.address.clone().into(),
+                })?
+                .1
+                .clone();
+
             // Get the code id of the module
             let proposal_code_id = if let Ok(contract_info) = deps
                 .querier
@@ -243,16 +268,16 @@ fn execute_migration_v1_v2(
             }?;
 
             // check if Code id is valid DAO DAO code id
-            if proposal_code_id == proposal_pairs[proposal_index].v1_code_id {
+            if proposal_code_id == proposal_pair.v1_code_id {
                 msgs.push(
                     WasmMsg::Migrate {
                         contract_addr: module.address.to_string(),
-                        new_code_id: proposal_pairs[proposal_index].v2_code_id,
-                        msg: to_binary(&proposal_pairs[proposal_index].migrate_msg).unwrap(),
+                        new_code_id: proposal_pair.v2_code_id,
+                        msg: to_binary(&proposal_pair.migrate_msg).unwrap(),
                     }
                     .into(),
                 );
-                modules_addrs.proposals.push(module.address);
+                modules_addrs.proposals.push(module.address.clone());
                 Ok(())
             } else {
                 // Return false because we couldn't find the code id on our list.
@@ -262,8 +287,7 @@ fn execute_migration_v1_v2(
             }?;
 
             Ok(())
-        },
-    )?;
+        })?;
 
     // We successfully verified all modules of the DAO, we can send migration msgs.
 
diff --git a/contracts/external/dao-migrator/src/error.rs b/contracts/external/dao-migrator/src/error.rs
index 3f113101..c99a5cc9 100644
--- a/contracts/external/dao-migrator/src/error.rs
+++ b/contracts/external/dao-migrator/src/error.rs
@@ -34,9 +34,15 @@ pub enum ContractError {
     #[error("Failed to verify any DAO proposal single module address")]
     DaoProposalSingleNotFound,
 
+    #[error("We couldn't find the proposal modules in provided migration params: {addr}")]
+    ProposalModuleNotFoundInParams { addr: String },
+
     #[error("Failed to verify proposal in {module_addr}")]
     NoProposalsOnModule { module_addr: String },
 
+    #[error("Duplicate params found for the same module")]
+    DuplicateProposalParams,
+
     #[error("Proposal migration params length is not equal to proposal modules length")]
     MigrationParamsNotEqualProposalModulesLength,
 }
diff --git a/contracts/external/dao-migrator/src/types.rs b/contracts/external/dao-migrator/src/types.rs
index 960431de..cdb0adb9 100644
--- a/contracts/external/dao-migrator/src/types.rs
+++ b/contracts/external/dao-migrator/src/types.rs
@@ -57,7 +57,7 @@ pub struct MigrationParams {
     /// migration will be aborted.
     pub migrate_stake_cw20_manager: Option<bool>,
     // dao_proposal_single
-    pub proposal_params: Vec<ProposalParams>,
+    pub proposal_params: Vec<(String, ProposalParams)>,
 }
 
 /// Wrapper enum that helps us to hold different types of migration msgs
@@ -71,6 +71,7 @@ pub enum MigrationMsgs {
 }
 
 /// Module data we need for migrations and tests.
+#[derive(Clone)]
 pub struct CodeIdPair {
     /// The code id used in V1 module
     pub v1_code_id: u64,
diff --git a/justfile b/justfile
index a8ba0837..beb5d293 100644
--- a/justfile
+++ b/justfile
@@ -28,7 +28,7 @@ integration-test: deploy-local workspace-optimize
 integration-test-dev test_name="":
 	SKIP_CONTRACT_STORE=true RUST_LOG=info CONFIG='{{`pwd`}}/ci/configs/cosm-orc/local.yaml' cargo integration-test {{test_name}}
 
-bootstrap-dev: deploy-local workspace-optimize
+bootstrap-dev: deploy-local workspace-optimize-arm
 	RUST_LOG=info CONFIG={{orc_config}} cargo run bootstrap-env
 
 deploy-local: download-deps
@@ -63,3 +63,10 @@ workspace-optimize:
 		--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
 		--platform linux/amd64 \
 		cosmwasm/workspace-optimizer:0.12.11
+
+workspace-optimize-arm:
+    docker run --rm -v "$(pwd)":/code \
+        --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
+        --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
+        --platform linux/arm64 \
+        cosmwasm/workspace-optimizer-arm64:0.12.11
diff --git a/typescript/contracts/dao-core/DaoCore.types.ts b/typescript/contracts/dao-core/DaoCore.types.ts
index 309a81ca..80856e91 100644
--- a/typescript/contracts/dao-core/DaoCore.types.ts
+++ b/typescript/contracts/dao-core/DaoCore.types.ts
@@ -366,7 +366,7 @@ export interface MigrateV1ToV2 {
 }
 export interface MigrationModuleParams {
   migrate_stake_cw20_manager?: boolean | null;
-  proposal_params: ProposalParams[];
+  proposal_params: [string, ProposalParams][];
 }
 export interface ProposalParams {
   close_proposal_on_execution_failure: boolean;


Test patch:
diff --git a/ci/integration-tests/src/helpers/helper.rs b/ci/integration-tests/src/helpers/helper.rs
index fe3de452..2ec139df 100644
--- a/ci/integration-tests/src/helpers/helper.rs
+++ b/ci/integration-tests/src/helpers/helper.rs
@@ -1,18 +1,21 @@
 use super::chain::Chain;
 use anyhow::Result;
 use cosm_orc::orchestrator::SigningKey;
-use cosmwasm_std::{to_binary, Decimal, Empty, Uint128};
+use cosmwasm_std::{to_binary, CosmosMsg, Decimal, Empty, Uint128};
 use cw20::Cw20Coin;
 use cw_utils::Duration;
 use dao_core::query::DumpStateResponse;
 use dao_interface::{Admin, ModuleInstantiateInfo};
 use dao_voting::{
     deposit::{DepositRefundPolicy, DepositToken, UncheckedDepositInfo},
-    pre_propose::PreProposeInfo,
+    pre_propose::{PreProposeInfo, ProposalCreationPolicy},
     threshold::PercentageThreshold,
     threshold::Threshold,
+    voting::Vote,
 };
 
+pub const DEPOSIT_AMOUNT: Uint128 = Uint128::new(1_000_000);
+
 #[derive(Debug)]
 pub struct DaoState {
     pub addr: String,
@@ -75,7 +78,7 @@ pub fn create_dao(
                         msg: to_binary(&dao_pre_propose_single::InstantiateMsg {
                             deposit_info: Some(UncheckedDepositInfo {
                                 denom: DepositToken::VotingModuleToken {},
-                                amount: Uint128::new(1000000000),
+                                amount: DEPOSIT_AMOUNT,
                                 refund_policy: DepositRefundPolicy::OnlyPassed,
                             }),
                             open_proposal_submission: false,
@@ -97,12 +100,194 @@ pub fn create_dao(
         .orc
         .instantiate("dao_core", op_name, &msg, key, None, vec![])?;
 
-    let res = chain
+    // add proposal, pre-propose, voting, cw20_stake, and cw20_base
+    // contracts to the orc contract map.
+
+    let state: DumpStateResponse = chain
+        .orc
+        .query("dao_core", &dao_core::msg::QueryMsg::DumpState {})?
+        .data()
+        .unwrap();
+    chain
+        .orc
+        .contract_map
+        .add_address(
+            "dao_proposal_single",
+            state.proposal_modules[0].address.to_string(),
+        )
+        .unwrap();
+
+    let ProposalCreationPolicy::Module { addr: pre_propose } = chain
+	.orc
+	.query(
+	    "dao_proposal_single",
+	    &dao_proposal_single::msg::QueryMsg::ProposalCreationPolicy {}
+	).unwrap()
+	.data()
+	.unwrap()
+    else {
+	panic!("expected pre-propose module")
+    };
+    chain
         .orc
-        .query("dao_core", &dao_core::msg::QueryMsg::DumpState {})?;
+        .contract_map
+        .add_address("dao_pre_propose_single", pre_propose)
+        .unwrap();
+
+    chain
+        .orc
+        .contract_map
+        .add_address("dao_voting_cw20_staked", state.voting_module.to_string())
+        .unwrap();
+    let cw20_stake: String = chain
+        .orc
+        .query(
+            "dao_voting_cw20_staked",
+            &dao_voting_cw20_staked::msg::QueryMsg::StakingContract {},
+        )
+        .unwrap()
+        .data()
+        .unwrap();
+    chain
+        .orc
+        .contract_map
+        .add_address("cw20_stake", cw20_stake)
+        .unwrap();
+    let cw20_base: String = chain
+        .orc
+        .query(
+            "dao_voting_cw20_staked",
+            &dao_voting_cw20_staked::msg::QueryMsg::TokenContract {},
+        )
+        .unwrap()
+        .data()
+        .unwrap();
+    chain
+        .orc
+        .contract_map
+        .add_address("cw20_base", cw20_base)
+        .unwrap();
 
     Ok(DaoState {
         addr: chain.orc.contract_map.address("dao_core")?,
-        state: res.data()?,
+        state,
     })
 }
+
+pub fn stake_tokens(chain: &mut Chain, how_many: u128, key: &SigningKey) {
+    chain
+        .orc
+        .execute(
+            "cw20_base",
+            "send_and_stake_cw20",
+            &cw20::Cw20ExecuteMsg::Send {
+                contract: chain.orc.contract_map.address("cw20_stake").unwrap(),
+                amount: Uint128::new(how_many),
+                msg: to_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(),
+            },
+            key,
+            vec![],
+        )
+        .unwrap();
+    chain
+        .orc
+        .poll_for_n_blocks(1, std::time::Duration::from_millis(20_000), false)
+        .unwrap();
+}
+
+pub fn create_proposal(
+    chain: &mut Chain,
+    msgs: Vec<CosmosMsg>,
+    key: &SigningKey,
+) -> Result<dao_proposal_single::query::ProposalResponse> {
+    let next_id: u64 = chain
+        .orc
+        .query(
+            "dao_proposal_single",
+            &dao_proposal_single::msg::QueryMsg::NextProposalId {},
+        )
+        .unwrap()
+        .data()
+        .unwrap();
+
+    // increase allowance to pay proposal deposit.
+    chain
+        .orc
+        .execute(
+            "cw20_base",
+            "cw20_base_increase_allowance",
+            &cw20::Cw20ExecuteMsg::IncreaseAllowance {
+                spender: chain
+                    .orc
+                    .contract_map
+                    .address("dao_pre_propose_single")
+                    .unwrap(),
+                amount: DEPOSIT_AMOUNT,
+                expires: None,
+            },
+            key,
+            vec![],
+        )
+        .unwrap();
+
+    chain
+        .orc
+        .execute(
+            "dao_pre_propose_single",
+            "pre_propose_propose",
+            &dao_pre_propose_single::ExecuteMsg::Propose {
+                msg: dao_pre_propose_single::ProposeMessage::Propose {
+                    title: "title".to_string(),
+                    description: "desc".to_string(),
+                    msgs,
+                },
+            },
+            key,
+            vec![],
+        )
+        .unwrap();
+
+    let r = chain
+        .orc
+        .query(
+            "dao_proposal_single",
+            &dao_proposal_single::msg::QueryMsg::Proposal {
+                proposal_id: next_id,
+            },
+        )
+        .unwrap()
+        .data()
+        .unwrap();
+
+    Ok(r)
+}
+
+pub fn vote(chain: &mut Chain, proposal_id: u64, vote: Vote, key: &SigningKey) {
+    chain
+        .orc
+        .execute(
+            "dao_proposal_single",
+            "dao_proposal_single_vote",
+            &dao_proposal_single::msg::ExecuteMsg::Vote {
+                proposal_id,
+                vote,
+                rationale: None,
+            },
+            key,
+            vec![],
+        )
+        .unwrap();
+}
+
+pub fn execute(chain: &mut Chain, proposal_id: u64, key: &SigningKey) {
+    chain
+        .orc
+        .execute(
+            "dao_proposal_single",
+            "dao_proposal_single_vote",
+            &dao_proposal_single::msg::ExecuteMsg::Execute { proposal_id },
+            key,
+            vec![],
+        )
+        .unwrap();
+}
diff --git a/ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs b/ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs
index 9969f65e..620f2d53 100644
--- a/ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs
+++ b/ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs
@@ -16,7 +16,7 @@ struct CommonTest {
     cw721: String,
 }
 
-fn instantiate_cw721_base(chain: &mut Chain, key: &SigningKey, minter: &str) -> String {
+pub fn instantiate_cw721_base(chain: &mut Chain, key: &SigningKey, minter: &str) -> String {
     chain
         .orc
         .instantiate(
@@ -64,7 +64,13 @@ fn setup_test(
     CommonTest { module, cw721 }
 }
 
-fn send_nft(chain: &mut Chain, sender: &SigningKey, receiver: &str, token_id: &str, msg: Binary) {
+pub fn send_nft(
+    chain: &mut Chain,
+    sender: &SigningKey,
+    receiver: &str,
+    token_id: &str,
+    msg: Binary,
+) {
     chain
         .orc
         .execute(
@@ -81,7 +87,7 @@ fn send_nft(chain: &mut Chain, sender: &SigningKey, receiver: &str, token_id: &s
         .unwrap();
 }
 
-fn mint_nft(chain: &mut Chain, sender: &SigningKey, receiver: &str, token_id: &str) {
+pub fn mint_nft(chain: &mut Chain, sender: &SigningKey, receiver: &str, token_id: &str) {
     chain
         .orc
         .execute(
@@ -99,7 +105,7 @@ fn mint_nft(chain: &mut Chain, sender: &SigningKey, receiver: &str, token_id: &s
         .unwrap();
 }
 
-fn unstake_nfts(chain: &mut Chain, sender: &SigningKey, token_ids: &[&str]) {
+pub fn unstake_nfts(chain: &mut Chain, sender: &SigningKey, token_ids: &[&str]) {
     chain
         .orc
         .execute(
@@ -114,7 +120,7 @@ fn unstake_nfts(chain: &mut Chain, sender: &SigningKey, token_ids: &[&str]) {
         .unwrap();
 }
 
-fn claim_nfts(chain: &mut Chain, sender: &SigningKey) {
+pub fn claim_nfts(chain: &mut Chain, sender: &SigningKey) {
     chain
         .orc
         .execute(
@@ -127,7 +133,7 @@ fn claim_nfts(chain: &mut Chain, sender: &SigningKey) {
         .unwrap();
 }
 
-fn query_voting_power(chain: &Chain, addr: &str, height: Option<u64>) -> Uint128 {
+pub fn query_voting_power(chain: &Chain, addr: &str, height: Option<u64>) -> Uint128 {
     let res = chain
         .orc
         .query(
@@ -142,7 +148,7 @@ fn query_voting_power(chain: &Chain, addr: &str, height: Option<u64>) -> Uint128
     data.power
 }
 
-fn mint_and_stake_nft(
+pub fn mint_and_stake_nft(
     chain: &mut Chain,
     sender_key: &SigningKey,
     sender: &str,
diff --git a/ci/integration-tests/src/tests/mod.rs b/ci/integration-tests/src/tests/mod.rs
index d2bdf9b5..f777d5e3 100644
--- a/ci/integration-tests/src/tests/mod.rs
+++ b/ci/integration-tests/src/tests/mod.rs
@@ -3,3 +3,5 @@ pub mod cw_core_test;
 pub mod cw20_stake_test;
 
 pub mod dao_voting_cw721_staked_test;
+
+pub mod proposal_gas_test;
diff --git a/ci/integration-tests/src/tests/proposal_gas_test.rs b/ci/integration-tests/src/tests/proposal_gas_test.rs
new file mode 100644
index 00000000..697125c8
--- /dev/null
+++ b/ci/integration-tests/src/tests/proposal_gas_test.rs
@@ -0,0 +1,67 @@
+use cosmwasm_std::{to_binary, CosmosMsg, Empty, WasmMsg};
+use cw721_base::MintMsg;
+use dao_proposal_single::query::ProposalResponse;
+use dao_voting::voting::Vote;
+use test_context::test_context;
+
+use crate::helpers::{
+    chain::Chain,
+    helper::{create_dao, create_proposal, execute, stake_tokens, vote},
+};
+
+use super::dao_voting_cw721_staked_test::instantiate_cw721_base;
+
+fn mint_mint_mint_mint(cw721: &str, owner: &str, mints: u64) -> Vec<CosmosMsg> {
+    (0..mints)
+        .map(|mint| {
+            WasmMsg::Execute {
+                contract_addr: cw721.to_string(),
+                msg: to_binary(&cw721_base::msg::ExecuteMsg::Mint::<Empty, Empty>(
+                    MintMsg::<Empty> {
+                        token_id: mint.to_string(),
+                        owner: owner.to_string(),
+                        token_uri: Some("https://bafkreibufednctf2f2bpduiibgkvpqcw5rtdmhqh2htqx3qbdnji4h55hy.ipfs.nftstorage.link".to_string()),
+                        extension: Empty::default(),
+                    },
+                ))
+                .unwrap(),
+                funds: vec![],
+            }
+            .into()
+        })
+        .collect()
+}
+
+/// tests that the maximum number of NFTs creatable in a proposal does
+/// not decrease over time. this test failing means that our proposal
+/// gas usage as gotten worse.
+#[test_context(Chain)]
+#[test]
+#[ignore]
+fn how_many_nfts_can_be_minted_in_one_proposal(chain: &mut Chain) {
+    let user_addr = chain.users["user1"].account.address.clone();
+    let user_key = chain.users["user1"].key.clone();
+
+    let dao = create_dao(chain, None, "create_dao", user_addr.clone(), &user_key).unwrap();
+    let cw721 = instantiate_cw721_base(chain, &user_key, &dao.addr);
+
+    // limit selected with tuning method below.
+    let msgs = mint_mint_mint_mint(&cw721, &user_addr, 55);
+    stake_tokens(chain, 1, &user_key);
+    let ProposalResponse { id, .. } = create_proposal(chain, msgs, &user_key).unwrap();
+    vote(chain, id, Vote::Yes, &user_key);
+    execute(chain, id, &user_key);
+
+    // for re-tuning the limit, this may be helpful:
+    //
+    // ```
+    // for i in 11..20 {
+    //     let msgs = mint_mint_mint_mint(&cw721, &user_addr, 5 * i);
+    //     stake_tokens(chain, 1, &user_key);
+    //     let ProposalResponse { id, .. } = create_proposal(chain, msgs, &user_key).unwrap();
+    //     vote(chain, id, Vote::Yes, &user_key);
+    //     execute(chain, id, &user_key);
+    //     eprintln!("minted {}", 5 * 11);
+    // }
+    // ```
+}
diff --git a/contracts/external/dao-migrator/src/testing/helpers.rs b/contracts/external/dao-migrator/src/testing/helpers.rs
index 20d81900..9e05af99 100644
--- a/contracts/external/dao-migrator/src/testing/helpers.rs
+++ b/contracts/external/dao-migrator/src/testing/helpers.rs
@@ -1,4 +1,7 @@
-use cosmwasm_std::{to_binary, Addr, Empty, Uint128, WasmMsg};
+use cosmwasm_std::{
+    to_binary, Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, Uint128,
+    WasmMsg,
+};
 use cw_multi_test::{next_block, App, Contract, ContractWrapper, Executor};
 use dao_core::query::SubDao;
 use dao_testing::contracts::{
@@ -6,7 +9,10 @@ use dao_testing::contracts::{
     dao_core_contract, proposal_single_contract, v1_dao_core_contract, v1_proposal_single_contract,
 };
 
-use crate::types::{V1CodeIds, V2CodeIds};
+use crate::{
+    types::{V1CodeIds, V2CodeIds},
+    ContractError,
+};
 
 pub(crate) const SENDER_ADDR: &str = "creator";
 
@@ -26,7 +32,7 @@ pub struct ExecuteParams {
     pub migrate_cw20: Option<bool>,
 }
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub struct ModuleAddrs {
     pub core: Addr,
     pub proposals: Vec<Addr>,
@@ -320,3 +326,27 @@ pub fn dao_voting_cw4_contract() -> Box<dyn Contract<Empty>> {
     .with_migrate(dao_voting_cw4::contract::migrate);
     Box::new(contract)
 }
+
+fn some_init(
+    _deps: DepsMut,
+    _env: Env,
+    _info: MessageInfo,
+    _msg: (),
+) -> Result<Response, ContractError> {
+    Ok(Response::default())
+}
+fn some_execute(
+    _deps: DepsMut,
+    _env: Env,
+    _info: MessageInfo,
+    _msg: (),
+) -> Result<Response, ContractError> {
+    Ok(Response::default())
+}
+fn some_query(_deps: Deps, _env: Env, _msg: ()) -> StdResult<Binary> {
+    Ok(Binary::default())
+}
+
+pub fn demo_contract() -> Box<dyn Contract<Empty>> {
+    Box::new(ContractWrapper::new(some_execute, some_init, some_query))
+}
diff --git a/contracts/external/dao-migrator/src/testing/setup.rs b/contracts/external/dao-migrator/src/testing/setup.rs
index 6f372b32..f0d0bcec 100644
--- a/contracts/external/dao-migrator/src/testing/setup.rs
+++ b/contracts/external/dao-migrator/src/testing/setup.rs
@@ -228,7 +228,7 @@ pub fn execute_migration(
     module_addrs: &ModuleAddrs,
     v1_code_ids: V1CodeIds,
     params: Option<ExecuteParams>,
-    multiple_proposals: bool,
+    custom_proposal_params: Option<Vec<(String, ProposalParams)>>,
 ) -> Result<AppResponse, anyhow::Error> {
     let sender = Addr::unchecked(SENDER_ADDR);
     let migrator_code_id = app.store_code(migrator_contract());
@@ -237,17 +237,25 @@ pub fn execute_migration(
         sub_daos: Some(vec![]),
         migrate_cw20: Some(true),
     });
-    let mut proposal_params = vec![ProposalParams {
-        close_proposal_on_execution_failure: true,
-        pre_propose_info: dao_voting::pre_propose::PreProposeInfo::AnyoneMayPropose {},
-    }];
-
-    if multiple_proposals {
-        proposal_params.push(ProposalParams {
-            close_proposal_on_execution_failure: true,
-            pre_propose_info: dao_voting::pre_propose::PreProposeInfo::AnyoneMayPropose {},
-        })
-    }
+
+    let proposal_params = if let Some(params) = custom_proposal_params {
+        params
+    } else {
+        module_addrs
+            .proposals
+            .iter()
+            .map(|addr| {
+                (
+                    addr.clone().into(),
+                    ProposalParams {
+                        close_proposal_on_execution_failure: true,
+                        pre_propose_info:
+                            dao_voting::pre_propose::PreProposeInfo::AnyoneMayPropose {},
+                    },
+                )
+            })
+            .collect::<Vec<(String, ProposalParams)>>()
+    };
 
     app.execute_contract(
         sender.clone(),
@@ -332,7 +340,6 @@ pub fn execute_migration_from_core(
     module_addrs: &ModuleAddrs,
     v1_code_ids: V1CodeIds,
     params: Option<ExecuteParams>,
-    multiple_proposals: bool,
 ) -> Result<AppResponse, anyhow::Error> {
     let sender = Addr::unchecked(SENDER_ADDR);
     let migrator_code_id = app.store_code(migrator_contract());
@@ -341,17 +348,20 @@ pub fn execute_migration_from_core(
         sub_daos: Some(vec![]),
         migrate_cw20: Some(true),
     });
-    let mut proposal_params = vec![dao_core::migrate_msg::ProposalParams {
-        close_proposal_on_execution_failure: true,
-        pre_propose_info: dao_core::migrate_msg::PreProposeInfo::AnyoneMayPropose {},
-    }];
-
-    if multiple_proposals {
-        proposal_params.push(dao_core::migrate_msg::ProposalParams {
-            close_proposal_on_execution_failure: true,
-            pre_propose_info: dao_core::migrate_msg::PreProposeInfo::AnyoneMayPropose {},
+
+    let proposal_params = module_addrs
+        .proposals
+        .iter()
+        .map(|addr| {
+            (
+                addr.clone().into(),
+                dao_core::migrate_msg::ProposalParams {
+                    close_proposal_on_execution_failure: true,
+                    pre_propose_info: dao_core::migrate_msg::PreProposeInfo::AnyoneMayPropose {},
+                },
+            )
         })
-    }
+        .collect::<Vec<(String, dao_core::migrate_msg::ProposalParams)>>();
 
     app.execute_contract(
         sender.clone(),
diff --git a/contracts/external/dao-migrator/src/testing/test_migration.rs b/contracts/external/dao-migrator/src/testing/test_migration.rs
index 20346f6e..228602bc 100644
--- a/contracts/external/dao-migrator/src/testing/test_migration.rs
+++ b/contracts/external/dao-migrator/src/testing/test_migration.rs
@@ -1,5 +1,7 @@
 use std::borrow::BorrowMut;
 
+use cosmwasm_std::Addr;
+use cw_multi_test::Executor;
 use dao_core::{query::SubDao, state::ProposalModuleStatus};
 
 use crate::{
@@ -11,10 +13,11 @@ use crate::{
             query_state_v1_cw20, query_state_v1_cw4, query_state_v2_cw20, query_state_v2_cw4,
         },
     },
+    types::ProposalParams,
     ContractError,
 };
 
-use super::setup::setup_dao_v1_multiple_proposals;
+use super::{helpers::demo_contract, setup::setup_dao_v1_multiple_proposals};
 
 pub fn basic_test(voting_type: VotingType, from_core: bool) {
     let (mut app, module_addrs, v1_code_ids) = setup_dao_v1(voting_type.clone());
@@ -36,11 +39,10 @@ pub fn basic_test(voting_type: VotingType, from_core: bool) {
 
     match from_core {
         true => {
-            execute_migration_from_core(app.borrow_mut(), &module_addrs, v1_code_ids, None, false)
-                .unwrap()
+            execute_migration_from_core(app.borrow_mut(), &module_addrs, v1_code_ids, None).unwrap()
         }
         false => {
-            execute_migration(app.borrow_mut(), &module_addrs, v1_code_ids, None, false).unwrap()
+            execute_migration(app.borrow_mut(), &module_addrs, v1_code_ids, None, None).unwrap()
         }
     };
 
@@ -85,6 +87,62 @@ fn test_execute_migration() {
     basic_test(VotingType::Cw4, true);
 }
 
+#[test]
+fn test_migrator_address_is_first() {
+    let (mut app, module_addrs, v1_code_ids) = setup_dao_v1(VotingType::Cw20);
+
+    // We init some demo contracts so we can bump the contract addr to "contract1X"
+    // That way, when we do a migration, the newely created migrator contract address
+    // will be "contract11", because the proposal module address is "contract4"
+    // when we query the dao for "ProposalModules", the migrator address
+    // will appear first in the list ("contract11" < "contract4")
+    let demo_code_id = app.store_code(demo_contract());
+    for _ in 0..6 {
+        app.instantiate_contract(
+            demo_code_id,
+            Addr::unchecked("some"),
+            &(),
+            &[],
+            "demo",
+            None,
+        )
+        .unwrap();
+    }
+
+    let mut test_state_v1 = query_state_v1_cw20(
+        &mut app,
+        module_addrs.proposals[0].clone(),
+        module_addrs.voting.clone(),
+    );
+    //NOTE: We add 1 to count because we create a new proposal in execute_migration
+    test_state_v1.proposal_count += 1;
+
+    execute_migration(app.borrow_mut(), &module_addrs, v1_code_ids, None, None).unwrap();
+
+    let test_state_v2 = query_state_v2_cw20(
+        &mut app,
+        module_addrs.proposals[0].clone(),
+        module_addrs.voting,
+    );
+
+    assert_eq!(test_state_v1, test_state_v2);
+
+    let modules: Vec<dao_core::state::ProposalModule> = app
+        .wrap()
+        .query_wasm_smart(
+            module_addrs.core,
+            &dao_core::msg::QueryMsg::ProposalModules {
+                start_after: None,
+                limit: None,
+            },
+        )
+        .unwrap();
+
+    assert_eq!(modules.len(), 2);
+    assert_eq!(modules[1].address, module_addrs.proposals[0]); // proposal module
+    assert_eq!(modules[0].status, ProposalModuleStatus::Disabled); // migrator module
+}
+
 #[test]
 fn test_multiple_proposal_modules() {
     let (mut app, module_addrs, v1_code_ids) = setup_dao_v1_multiple_proposals();
@@ -97,7 +155,7 @@ fn test_multiple_proposal_modules() {
     //NOTE: We add 1 to count because we create a new proposal in execute_migration
     test_state_v1.proposal_count += 1;
 
-    execute_migration(app.borrow_mut(), &module_addrs, v1_code_ids, None, true).unwrap();
+    execute_migration(app.borrow_mut(), &module_addrs, v1_code_ids, None, None).unwrap();
 
     let test_state_v2 = query_state_v2_cw20(
         &mut app,
@@ -108,12 +166,52 @@ fn test_multiple_proposal_modules() {
     assert_eq!(test_state_v1, test_state_v2);
 }
 
+#[test]
+fn test_duplicate_proposal_params() {
+    let (mut app, module_addrs, v1_code_ids) = setup_dao_v1_multiple_proposals();
+
+    // 2 pararms with the same addr
+    let custom_params = vec![
+        (
+            module_addrs.proposals[0].to_string(),
+            ProposalParams {
+                close_proposal_on_execution_failure: true,
+                pre_propose_info: dao_voting::pre_propose::PreProposeInfo::AnyoneMayPropose {},
+            },
+        ),
+        (
+            module_addrs.proposals[0].to_string(),
+            ProposalParams {
+                close_proposal_on_execution_failure: true,
+                pre_propose_info: dao_voting::pre_propose::PreProposeInfo::AnyoneMayPropose {},
+            },
+        ),
+    ];
+
+    let err = execute_migration(
+        app.borrow_mut(),
+        &module_addrs,
+        v1_code_ids,
+        None,
+        Some(custom_params),
+    )
+    .unwrap_err()
+    .downcast::<ContractError>()
+    .unwrap();
+
+    assert_eq!(err, ContractError::DuplicateProposalParams)
+}
+
 #[test]
 fn test_multiple_proposal_modules_failing() {
     // Test single proposal with multiple proposal params.
-    let (mut app, module_addrs, v1_code_ids) = setup_dao_v1(VotingType::Cw20);
+    let (mut app, mut module_addrs, v1_code_ids) = setup_dao_v1(VotingType::Cw20);
 
-    let err = execute_migration(app.borrow_mut(), &module_addrs, v1_code_ids, None, true)
+    // `module_addrs.proposals` is only used to set migration params based on how many proposals we have here
+    // Its safe to add/remove 2nd proposal in this tests because the actual proposals are taken within the contract
+    // and are not provided externally
+    module_addrs.proposals.push(Addr::unchecked("proposal2"));
+    let err = execute_migration(app.borrow_mut(), &module_addrs, v1_code_ids, None, None)
         .unwrap_err()
         .downcast::<ContractError>()
         .unwrap();
@@ -123,9 +221,10 @@ fn test_multiple_proposal_modules_failing() {
     );
 
     // Test multiple proposals with single proposal params.
-    let (mut app, module_addrs, v1_code_ids) = setup_dao_v1_multiple_proposals();
+    let (mut app, mut module_addrs, v1_code_ids) = setup_dao_v1_multiple_proposals();
 
-    let err = execute_migration(app.borrow_mut(), &module_addrs, v1_code_ids, None, false)
+    module_addrs.proposals.remove(1);
+    let err = execute_migration(app.borrow_mut(), &module_addrs, v1_code_ids, None, None)
         .unwrap_err()
         .downcast::<ContractError>()
         .unwrap();
@@ -140,7 +239,7 @@ fn test_wrong_code_id() {
     let (mut app, module_addrs, mut v1_code_ids) = setup_dao_v1(VotingType::Cw20);
     let old_v1_code_ids = v1_code_ids.clone();
     v1_code_ids.proposal_single = 555;
-    let err = execute_migration(app.borrow_mut(), &module_addrs, v1_code_ids, None, false)
+    let err = execute_migration(app.borrow_mut(), &module_addrs, v1_code_ids, None, None)
         .unwrap_err()
         .downcast::<ContractError>()
         .unwrap();
@@ -154,7 +253,7 @@ fn test_wrong_code_id() {
     let (mut app, module_addrs, mut v1_code_ids) = setup_dao_v1(VotingType::Cw20);
     let old_v1_code_ids = v1_code_ids.clone();
     v1_code_ids.cw20_stake = 555;
-    let err = execute_migration(app.borrow_mut(), &module_addrs, v1_code_ids, None, false)
+    let err = execute_migration(app.borrow_mut(), &module_addrs, v1_code_ids, None, None)
         .unwrap_err()
         .downcast::<ContractError>()
         .unwrap();
@@ -167,7 +266,7 @@ fn test_wrong_code_id() {
 
     let (mut app, module_addrs, mut v1_code_ids) = setup_dao_v1(VotingType::Cw20);
     v1_code_ids.cw20_staked_balances_voting = 555;
-    let err = execute_migration(app.borrow_mut(), &module_addrs, v1_code_ids, None, false)
+    let err = execute_migration(app.borrow_mut(), &module_addrs, v1_code_ids, None, None)
         .unwrap_err()
         .downcast::<ContractError>()
         .unwrap();
@@ -175,7 +274,7 @@ fn test_wrong_code_id() {
 
     let (mut app, module_addrs, mut v1_code_ids) = setup_dao_v1(VotingType::Cw4);
     v1_code_ids.cw4_voting = 555;
-    let err = execute_migration(app.borrow_mut(), &module_addrs, v1_code_ids, None, false)
+    let err = execute_migration(app.borrow_mut(), &module_addrs, v1_code_ids, None, None)
         .unwrap_err()
         .downcast::<ContractError>()
         .unwrap();
@@ -194,7 +293,7 @@ fn test_dont_migrate_cw20() {
             sub_daos: Some(vec![]),
             migrate_cw20: None,
         }),
-        false,
+        None,
     )
     .unwrap_err()
     .downcast::<ContractError>()
@@ -209,7 +308,7 @@ fn test_dont_migrate_cw20() {
             sub_daos: Some(vec![]),
             migrate_cw20: Some(false),
         }),
-        false,
+        None,
     )
     .unwrap_err()
     .downcast::<ContractError>()
@@ -233,7 +332,7 @@ fn test_sub_daos() {
             sub_daos: Some(vec![sub_dao.clone()]),
             migrate_cw20: Some(true),
         }),
-        false,
+        None,
     )
     .unwrap();