Repository: DA0-DA0/dao-contracts
Pull request #226 (2022-03-26 03:26:58)

Initial items on DAO Instantiation
Part 2 of #214

Allow the DAO to set an initial list of items on instantiation. These items can be:
- already existing addresses
- info used to instantiate a new contract, storing the corresponding address automatically



Patch:
diff --git a/contracts/cw-governance/schema/dump_state_response.json b/contracts/cw-governance/schema/dump_state_response.json
index e069865b..739593d6 100644
--- a/contracts/cw-governance/schema/dump_state_response.json
+++ b/contracts/cw-governance/schema/dump_state_response.json
@@ -1,7 +1,7 @@
 {
   "$schema": "http://json-schema.org/draft-07/schema#",
   "title": "DumpStateResponse",
-  "description": "Relevant state for the governance module.",
+  "description": "Relevant state for the governance module. Returned by the `DumpState` query.",
   "type": "object",
   "required": [
     "config",
diff --git a/contracts/cw-governance/schema/execute_msg.json b/contracts/cw-governance/schema/execute_msg.json
index c967693a..d887fb8a 100644
--- a/contracts/cw-governance/schema/execute_msg.json
+++ b/contracts/cw-governance/schema/execute_msg.json
@@ -98,18 +98,58 @@
         }
       },
       "additionalProperties": false
+    },
+    {
+      "description": "Adds an item to the governance contract's item map. If the item already exists the existing value is overriden. If the item does not exist a new item is added.",
+      "type": "object",
+      "required": [
+        "set_item"
+      ],
+      "properties": {
+        "set_item": {
+          "type": "object",
+          "required": [
+            "addr",
+            "key"
+          ],
+          "properties": {
+            "addr": {
+              "type": "string"
+            },
+            "key": {
+              "type": "string"
+            }
+          }
+        }
+      },
+      "additionalProperties": false
+    },
+    {
+      "description": "Removes an item from the governance contract's item map.",
+      "type": "object",
+      "required": [
+        "remove_item"
+      ],
+      "properties": {
+        "remove_item": {
+          "type": "object",
+          "required": [
+            "key"
+          ],
+          "properties": {
+            "key": {
+              "type": "string"
+            }
+          }
+        }
+      },
+      "additionalProperties": false
     }
   ],
   "definitions": {
     "Admin": {
       "description": "Information about the admin of a contract.",
       "oneOf": [
-        {
-          "type": "string",
-          "enum": [
-            "none"
-          ]
-        },
         {
           "description": "A specific address.",
           "type": "object",
@@ -143,6 +183,19 @@
             }
           },
           "additionalProperties": false
+        },
+        {
+          "description": "No admin.",
+          "type": "object",
+          "required": [
+            "none"
+          ],
+          "properties": {
+            "none": {
+              "type": "object"
+            }
+          },
+          "additionalProperties": false
         }
       ]
     },
diff --git a/contracts/cw-governance/schema/instantiate_msg.json b/contracts/cw-governance/schema/instantiate_msg.json
index 173b1a42..5b7a4089 100644
--- a/contracts/cw-governance/schema/instantiate_msg.json
+++ b/contracts/cw-governance/schema/instantiate_msg.json
@@ -27,6 +27,16 @@
         "null"
       ]
     },
+    "initial_items": {
+      "description": "Initial information for arbitrary contract addresses to be added to the items map. The key is the name of the item in the items map. The value is an enum that either uses an existing address or instantiates a new contract.",
+      "type": [
+        "array",
+        "null"
+      ],
+      "items": {
+        "$ref": "#/definitions/InitialItem"
+      }
+    },
     "name": {
       "description": "The name of the governance contract.",
       "type": "string"
@@ -44,12 +54,6 @@
     "Admin": {
       "description": "Information about the admin of a contract.",
       "oneOf": [
-        {
-          "type": "string",
-          "enum": [
-            "none"
-          ]
-        },
         {
           "description": "A specific address.",
           "type": "object",
@@ -83,6 +87,19 @@
             }
           },
           "additionalProperties": false
+        },
+        {
+          "description": "No admin.",
+          "type": "object",
+          "required": [
+            "none"
+          ],
+          "properties": {
+            "none": {
+              "type": "object"
+            }
+          },
+          "additionalProperties": false
         }
       ]
     },
@@ -90,6 +107,73 @@
       "description": "Binary is a wrapper around Vec<u8> to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec<u8>",
       "type": "string"
     },
+    "InitialItem": {
+      "type": "object",
+      "required": [
+        "info",
+        "name"
+      ],
+      "properties": {
+        "info": {
+          "description": "The info from which to derive the address.",
+          "allOf": [
+            {
+              "$ref": "#/definitions/InitialItemInfo"
+            }
+          ]
+        },
+        "name": {
+          "description": "The name of the item.",
+          "type": "string"
+        }
+      }
+    },
+    "InitialItemInfo": {
+      "oneOf": [
+        {
+          "description": "An existing contract address.",
+          "type": "object",
+          "required": [
+            "Existing"
+          ],
+          "properties": {
+            "Existing": {
+              "type": "object",
+              "required": [
+                "address"
+              ],
+              "properties": {
+                "address": {
+                  "type": "string"
+                }
+              }
+            }
+          },
+          "additionalProperties": false
+        },
+        {
+          "description": "Info for instantiating a new contract.",
+          "type": "object",
+          "required": [
+            "Instantiate"
+          ],
+          "properties": {
+            "Instantiate": {
+              "type": "object",
+              "required": [
+                "info"
+              ],
+              "properties": {
+                "info": {
+                  "$ref": "#/definitions/ModuleInstantiateInfo"
+                }
+              }
+            }
+          },
+          "additionalProperties": false
+        }
+      ]
+    },
     "ModuleInstantiateInfo": {
       "description": "Information needed to instantiate a governance or voting module.",
       "type": "object",
diff --git a/contracts/cw-governance/schema/query_msg.json b/contracts/cw-governance/schema/query_msg.json
index 9c60c92e..08f5814d 100644
--- a/contracts/cw-governance/schema/query_msg.json
+++ b/contracts/cw-governance/schema/query_msg.json
@@ -69,6 +69,116 @@
         }
       },
       "additionalProperties": false
+    },
+    {
+      "type": "object",
+      "required": [
+        "get_item"
+      ],
+      "properties": {
+        "get_item": {
+          "type": "object",
+          "required": [
+            "key"
+          ],
+          "properties": {
+            "key": {
+              "type": "string"
+            }
+          }
+        }
+      },
+      "additionalProperties": false
+    },
+    {
+      "type": "object",
+      "required": [
+        "list_items"
+      ],
+      "properties": {
+        "list_items": {
+          "type": "object",
+          "properties": {
+            "limit": {
+              "type": [
+                "integer",
+                "null"
+              ],
+              "format": "uint64",
+              "minimum": 0.0
+            },
+            "start_at": {
+              "type": [
+                "string",
+                "null"
+              ]
+            }
+          }
+        }
+      },
+      "additionalProperties": false
+    },
+    {
+      "type": "object",
+      "required": [
+        "voting_power_at_height"
+      ],
+      "properties": {
+        "voting_power_at_height": {
+          "type": "object",
+          "required": [
+            "address"
+          ],
+          "properties": {
+            "address": {
+              "type": "string"
+            },
+            "height": {
+              "type": [
+                "integer",
+                "null"
+              ],
+              "format": "uint64",
+              "minimum": 0.0
+            }
+          }
+        }
+      },
+      "additionalProperties": false
+    },
+    {
+      "type": "object",
+      "required": [
+        "total_power_at_height"
+      ],
+      "properties": {
+        "total_power_at_height": {
+          "type": "object",
+          "properties": {
+            "height": {
+              "type": [
+                "integer",
+                "null"
+              ],
+              "format": "uint64",
+              "minimum": 0.0
+            }
+          }
+        }
+      },
+      "additionalProperties": false
+    },
+    {
+      "type": "object",
+      "required": [
+        "info"
+      ],
+      "properties": {
+        "info": {
+          "type": "object"
+        }
+      },
+      "additionalProperties": false
     }
   ]
 }
diff --git a/contracts/cw-governance/src/contract.rs b/contracts/cw-governance/src/contract.rs
index 16bd3f73..544e0bac 100644
--- a/contracts/cw-governance/src/contract.rs
+++ b/contracts/cw-governance/src/contract.rs
@@ -1,3 +1,5 @@
+use std::convert::TryInto;
+
 #[cfg(not(feature = "library"))]
 use cosmwasm_std::entry_point;
 use cosmwasm_std::{
@@ -11,10 +13,11 @@ use cw_utils::parse_reply_instantiate_data;
 use cw_governance_interface::voting;
 
 use crate::error::ContractError;
-use crate::msg::{ExecuteMsg, InstantiateMsg, ModuleInstantiateInfo, QueryMsg};
+use crate::msg::{ExecuteMsg, InitialItemInfo, InstantiateMsg, ModuleInstantiateInfo, QueryMsg};
 use crate::query::{DumpStateResponse, GetItemResponse};
 use crate::state::{
-    Config, CONFIG, GOVERNANCE_MODULES, GOVERNANCE_MODULE_COUNT, ITEMS, VOTING_MODULE,
+    Config, CONFIG, GOVERNANCE_MODULES, GOVERNANCE_MODULE_COUNT, ITEMS,
+    PENDING_ITEM_INSTANTIATION_NAMES, VOTING_MODULE,
 };
 
 // version info for migration info
@@ -25,6 +28,15 @@ const GOV_MODULE_REPLY_ID: u64 = 0;
 const VOTE_MODULE_INSTANTIATE_REPLY_ID: u64 = 1;
 const VOTE_MODULE_UPDATE_REPLY_ID: u64 = 2;
 
+// Start at this ID since the items to instantiate on the instantiation
+// of this contract can be arbitrarily long. Everything with a reply ID
+// greater than or equal to this value will be considered an instantiated
+// item to store in the items map.
+const PENDING_ITEM_REPLY_ID_START: u64 = 100;
+// The maximum number of items that can be instantiated when this
+// contract is instantiated.
+const MAX_ITEM_INSTANTIATIONS_ON_INSTANTIATE: u64 = u64::MAX - PENDING_ITEM_REPLY_ID_START;
+
 #[cfg_attr(not(feature = "library"), entry_point)]
 pub fn instantiate(
     deps: DepsMut,
@@ -59,11 +71,52 @@ pub fn instantiate(
 
     GOVERNANCE_MODULE_COUNT.save(deps.storage, &(gov_module_msgs.len() as u64))?;
 
+    // Add or instantiate items if any are present.
+    let mut instantiate_item_msgs: Vec<SubMsg<Empty>> = vec![];
+    if let Some(items) = msg.initial_items {
+        if !items.is_empty() {
+            if items.len() > MAX_ITEM_INSTANTIATIONS_ON_INSTANTIATE.try_into().unwrap() {
+                return Err(ContractError::TooManyItems(
+                    MAX_ITEM_INSTANTIATIONS_ON_INSTANTIATE,
+                    items.len(),
+                ));
+            }
+
+            for (idx, item) in items.into_iter().enumerate() {
+                match item.info {
+                    // Use existing address.
+                    InitialItemInfo::Existing { address } => {
+                        let addr = deps.api.addr_validate(&address)?;
+                        ITEMS.save(deps.storage, item.name, &addr)?;
+                    }
+                    // Instantiate new contract and capture address on successful reply.
+                    InitialItemInfo::Instantiate { info } => {
+                        // Offset reply ID with index.
+                        let reply_id = PENDING_ITEM_REPLY_ID_START + idx as u64;
+
+                        // Create and add submessage.
+                        let item_msg = info.into_wasm_msg(env.contract.address.clone());
+                        let item_msg: SubMsg<Empty> = SubMsg::reply_on_success(item_msg, reply_id);
+                        instantiate_item_msgs.push(item_msg);
+
+                        // Store name in map for later retrieval if the contract instantiation succeeds.
+                        PENDING_ITEM_INSTANTIATION_NAMES.save(
+                            deps.storage,
+                            reply_id,
+                            &item.name,
+                        )?;
+                    }
+                }
+            }
+        }
+    }
+
     Ok(Response::new()
         .add_attribute("action", "instantiate")
         .add_attribute("sender", info.sender)
         .add_submessage(vote_module_msg)
-        .add_submessages(gov_module_msgs))
+        .add_submessages(gov_module_msgs)
+        .add_submessages(instantiate_item_msgs))
 }
 
 #[cfg_attr(not(feature = "library"), entry_point)]
@@ -352,6 +405,20 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractE
 
             Ok(Response::default().add_attribute("voting_module", vote_module_addr))
         }
+        reply_id if reply_id >= PENDING_ITEM_REPLY_ID_START => {
+            // Retrieve the name using the ID. If it doesn't exist,
+            // we didn't expect this reply or it was a redundant execution.
+            let item_name = PENDING_ITEM_INSTANTIATION_NAMES.load(deps.storage, reply_id)?;
+
+            let res = parse_reply_instantiate_data(msg)?;
+            let item_addr = deps.api.addr_validate(&res.contract_address)?;
+
+            ITEMS.save(deps.storage, item_name, &item_addr)?;
+            // Remove from pending map since we now have the contract address.
+            PENDING_ITEM_INSTANTIATION_NAMES.remove(deps.storage, reply_id);
+
+            Ok(Response::default().add_attribute("item", item_addr))
+        }
         _ => Err(ContractError::UnknownReplyID {}),
     }
 }
diff --git a/contracts/cw-governance/src/error.rs b/contracts/cw-governance/src/error.rs
index 344570cb..16d6208c 100644
--- a/contracts/cw-governance/src/error.rs
+++ b/contracts/cw-governance/src/error.rs
@@ -27,4 +27,7 @@ pub enum ContractError {
 
     #[error("Unsigned integer overflow.")]
     Overflow {},
+
+    #[error("You can only instantiate {0} items during instantiation, but you tried to instantiate {1}.")]
+    TooManyItems(u64, usize),
 }
diff --git a/contracts/cw-governance/src/msg.rs b/contracts/cw-governance/src/msg.rs
index 1b89d96b..7a607626 100644
--- a/contracts/cw-governance/src/msg.rs
+++ b/contracts/cw-governance/src/msg.rs
@@ -32,6 +32,22 @@ pub struct ModuleInstantiateInfo {
     pub label: String,
 }
 
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+pub enum InitialItemInfo {
+    /// An existing contract address.
+    Existing { address: String },
+    /// Info for instantiating a new contract.
+    Instantiate { info: ModuleInstantiateInfo },
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+pub struct InitialItem {
+    /// The name of the item.
+    pub name: String,
+    /// The info from which to derive the address.
+    pub info: InitialItemInfo,
+}
+
 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
 pub struct InstantiateMsg {
     /// The name of the governance contract.
@@ -47,6 +63,12 @@ pub struct InstantiateMsg {
     /// Instantiate information for the governance contract's
     /// governance modules.
     pub governance_modules_instantiate_info: Vec<ModuleInstantiateInfo>,
+
+    /// Initial information for arbitrary contract addresses to be
+    /// added to the items map. The key is the name of the item in the
+    /// items map. The value is an enum that either uses an existing
+    /// address or instantiates a new contract.
+    pub initial_items: Option<Vec<InitialItem>>,
 }
 
 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
diff --git a/contracts/cw-governance/src/state.rs b/contracts/cw-governance/src/state.rs
index ed7b3da1..4c3e5fbd 100644
--- a/contracts/cw-governance/src/state.rs
+++ b/contracts/cw-governance/src/state.rs
@@ -15,6 +15,8 @@ pub const CONFIG: Item<Config> = Item::new("config");
 pub const VOTING_MODULE: Item<Addr> = Item::new("voting_module");
 pub const GOVERNANCE_MODULES: Map<Addr, Empty> = Map::new("governance_modules");
 pub const ITEMS: Map<String, Addr> = Map::new("items");
+pub const PENDING_ITEM_INSTANTIATION_NAMES: Map<u64, String> =
+    Map::new("pending_item_instantiations");
 
 /// Stores the number of governance modules present in the governance
 /// contract. This information is avaliable from the governance


Test patch:
diff --git a/contracts/cw-governance/src/tests.rs b/contracts/cw-governance/src/tests.rs
index 312c4242..974f18a2 100644
--- a/contracts/cw-governance/src/tests.rs
+++ b/contracts/cw-governance/src/tests.rs
@@ -4,7 +4,10 @@ use cw_governance_interface::voting::VotingPowerAtHeightResponse;
 use cw_multi_test::{App, Contract, ContractWrapper, Executor};
 
 use crate::{
-    msg::{Admin, ExecuteMsg, InstantiateMsg, ModuleInstantiateInfo, QueryMsg},
+    msg::{
+        Admin, ExecuteMsg, InitialItem, InitialItemInfo, InstantiateMsg, ModuleInstantiateInfo,
+        QueryMsg,
+    },
     query::{DumpStateResponse, GetItemResponse},
     state::Config,
     ContractError,
@@ -93,6 +96,7 @@ fn test_instantiate_with_n_gov_modules(n: usize) {
                 label: format!("governance module {}", n),
             })
             .collect(),
+        initial_items: None,
     };
     let gov_addr = instantiate_gov(&mut app, gov_id, instantiate);
 
@@ -186,6 +190,7 @@ makes wickedness."
             label: "voting module".to_string(),
         },
         governance_modules_instantiate_info: governance_modules,
+        initial_items: None,
     };
     instantiate_gov(&mut app, gov_id, instantiate);
 }
@@ -216,6 +221,7 @@ fn test_update_config() {
             admin: Admin::GovernanceContract {},
             label: "voting module".to_string(),
         }],
+        initial_items: None,
     };
 
     let gov_addr = app
@@ -299,6 +305,7 @@ fn test_swap_governance(swaps: Vec<(u64, u64)>) {
             admin: Admin::GovernanceContract {},
             label: "governance module".to_string(),
         }],
+        initial_items: None,
     };
 
     let gov_addr = app
@@ -426,6 +433,7 @@ fn test_swap_voting_module() {
             admin: Admin::GovernanceContract {},
             label: "governance module".to_string(),
         }],
+        initial_items: None,
     };
 
     let gov_addr = app
@@ -524,6 +532,7 @@ fn test_permissions() {
             admin: Admin::GovernanceContract {},
             label: "governance module".to_string(),
         }],
+        initial_items: None,
     };
 
     let gov_addr = app
@@ -614,6 +623,7 @@ fn test_passthrough_voting_queries() {
             admin: Admin::GovernanceContract {},
             label: "governance module".to_string(),
         }],
+        initial_items: None,
     };
 
     let gov_addr = app
@@ -678,7 +688,7 @@ fn list_items(
     gov_addr: Addr,
     start_at: Option<String>,
     limit: Option<u64>,
-) -> Vec<Addr> {
+) -> Vec<String> {
     app.wrap()
         .query_wasm_smart(gov_addr, &QueryMsg::ListItems { start_at, limit })
         .unwrap()
@@ -726,6 +736,7 @@ fn test_add_remove_get() {
             admin: Admin::GovernanceContract {},
             label: "governance module".to_string(),
         }],
+        initial_items: None,
     };
 
     let gov_addr = app
@@ -744,14 +755,14 @@ fn test_add_remove_get() {
     set_item(
         &mut app,
         gov_addr.clone(),
-        "aaaaa".to_string(),
-        "aaaaa".to_string(),
+        "aaaaakey".to_string(),
+        "aaaaaaddr".to_string(),
     );
-    let a = get_item(&mut app, gov_addr.clone(), "aaaaa".to_string());
+    let a = get_item(&mut app, gov_addr.clone(), "aaaaakey".to_string());
     assert_eq!(
         a,
         GetItemResponse {
-            item: Some(Addr::unchecked("aaaaa"))
+            item: Some(Addr::unchecked("aaaaaaddr"))
         }
     );
 
@@ -804,6 +815,7 @@ fn test_list_items() {
             admin: Admin::GovernanceContract {},
             label: "governance module".to_string(),
         }],
+        initial_items: None,
     };
 
     let gov_addr = app
@@ -820,19 +832,19 @@ fn test_list_items() {
     set_item(
         &mut app,
         gov_addr.clone(),
-        "foo".to_string(),
-        "foo".to_string(),
+        "fookey".to_string(),
+        "fooaddr".to_string(),
     );
     set_item(
         &mut app,
         gov_addr.clone(),
-        "bar".to_string(),
-        "bar".to_string(),
+        "barkey".to_string(),
+        "baraddr".to_string(),
     );
 
     let first_item = list_items(&mut app, gov_addr.clone(), None, Some(1));
     assert_eq!(first_item.len(), 1);
-    assert_eq!(first_item[0], "foo".to_string());
+    assert_eq!(first_item[0], "fookey".to_string());
 
     let no_items = list_items(&mut app, gov_addr.clone(), None, Some(0));
     assert_eq!(no_items.len(), 0);
@@ -841,5 +853,112 @@ fn test_list_items() {
     // no limit ought to give us a single item.
     let second_item = list_items(&mut app, gov_addr, Some("foo".to_string()), None);
     assert_eq!(second_item.len(), 1);
-    assert_eq!(second_item[0], "foo".to_string());
+    assert_eq!(second_item[0], "fookey".to_string());
+}
+
+#[test]
+fn test_instantiate_with_items() {
+    let mut app = App::default();
+    let govmod_id = app.store_code(sudo_govmod_contract());
+    let voting_id = app.store_code(cw20_balances_voting());
+    let gov_id = app.store_code(cw_gov_contract());
+    let cw20_id = app.store_code(cw20_contract());
+
+    let govmod_instantiate = cw_govmod_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,
+        },
+    };
+
+    let gov_instantiate = InstantiateMsg {
+        name: "DAO DAO".to_string(),
+        description: "A DAO that builds DAOs.".to_string(),
+        image_url: None,
+        voting_module_instantiate_info: ModuleInstantiateInfo {
+            code_id: voting_id,
+            msg: to_binary(&voting_instantiate).unwrap(),
+            admin: Admin::GovernanceContract {},
+            label: "voting module".to_string(),
+        },
+        governance_modules_instantiate_info: vec![ModuleInstantiateInfo {
+            code_id: govmod_id,
+            msg: to_binary(&govmod_instantiate).unwrap(),
+            admin: Admin::GovernanceContract {},
+            label: "governance module".to_string(),
+        }],
+        initial_items: Some(vec![
+            InitialItem {
+                name: "item0".to_string(),
+                info: InitialItemInfo::Instantiate {
+                    info: ModuleInstantiateInfo {
+                        code_id: voting_id,
+                        msg: to_binary(&voting_instantiate).unwrap(),
+                        admin: Admin::GovernanceContract {},
+                        label: "item0: a voting module".to_string(),
+                    },
+                },
+            },
+            InitialItem {
+                name: "item1".to_string(),
+                info: InitialItemInfo::Instantiate {
+                    info: ModuleInstantiateInfo {
+                        code_id: voting_id,
+                        msg: to_binary(&voting_instantiate).unwrap(),
+                        admin: Admin::GovernanceContract {},
+                        label: "item1: another voting module".to_string(),
+                    },
+                },
+            },
+            InitialItem {
+                name: "item2".to_string(),
+                info: InitialItemInfo::Existing {
+                    address: "item2_addr".to_string(),
+                },
+            },
+        ]),
+    };
+
+    let gov_addr = app
+        .instantiate_contract(
+            gov_id,
+            Addr::unchecked(CREATOR_ADDR),
+            &gov_instantiate,
+            &[],
+            "cw-governance",
+            None,
+        )
+        .unwrap();
+
+    // Ensure initial items were added.
+    let items = list_items(&mut app, gov_addr.clone(), None, None);
+    assert_eq!(items.len(), 3);
+
+    // Descending order, so item2 is first.
+    assert_eq!(items[0], "item2".to_string());
+    let get_item2 = get_item(&mut app, gov_addr.clone(), "item2".to_string());
+    assert_eq!(
+        get_item2,
+        GetItemResponse {
+            item: Some(Addr::unchecked("item2_addr")),
+        }
+    );
+
+    assert_eq!(items[1], "item1".to_string());
+    let item1 = get_item(&mut app, gov_addr, "item1".to_string())
+        .item
+        .unwrap();
+    // Tests instantiate contracts with addresses in the form "Contract #{index}"
+    assert!(item1.to_string().starts_with("Contract #"));
 }
diff --git a/contracts/cw-govmod-single/src/tests.rs b/contracts/cw-govmod-single/src/tests.rs
index e22bce37..74cc89f7 100644
--- a/contracts/cw-govmod-single/src/tests.rs
+++ b/contracts/cw-govmod-single/src/tests.rs
@@ -114,6 +114,7 @@ fn instantiate_with_default_governance(
             admin: cw_governance::msg::Admin::GovernanceContract {},
             label: "DAO DAO governance module".to_string(),
         }],
+        initial_items: None,
     };
 
     instantiate_governance(app, governance_id, governance_instantiate)