Repository: DA0-DA0/dao-contracts
Pull request #467 (2022-08-26 23:33:48)

Implement DAOstar standard: https://daostar.one/EIP

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

```diff
@@           Coverage Diff           @@
##             main     #467   +/-   ##
=======================================
  Coverage   95.92%   95.93%           
=======================================
  Files          34       34           
  Lines        5475     5480    +5     
=======================================
+ Hits         5252     5257    +5     
  Misses        223      223           
```


| [Impacted Files](https://codecov.io/gh/DA0-DA0/dao-contracts/pull/467?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/467/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DA0-DA0#diff-Y29udHJhY3RzL2N3LWNvcmUvc3JjL2NvbnRyYWN0LnJz) | `93.46% <100.00%> (+0.07%)` | :arrow_up: |

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)

github-actions[bot]: ![gas](https://liquipedia.net/commons/images/thumb/7/7e/Scr-gas-t.png/20px-Scr-gas-t.png)     Cosm-Orc Gas Usage Report     ![gas](https://liquipedia.net/commons/images/thumb/7/7e/Scr-gas-t.png/20px-Scr-gas-t.png)
  
| Contract | Op Name | Gas Used | Old Gas Used | Gas Diff | File | 
| --- | --- | --- | --- | --- | --- |
| cw_core | Store__Store | 45256290 | 44938280 | +0.7077% | ci/integration_tests/src/helpers/chain.rs:82 |


<details><summary>Raw Report for f297fa9b9ac835ca93c55cee6c1b521591547bf5</summary>

| Contract | Op Name | Gas Used | Gas Wanted | File | 
| --- | --- | --- | --- | --- |
| cw_core | Execute__exc_items_rm | 192600 | 264389 | ci/integration_tests/src/tests/cw_core_test.rs:169 |
| cw_core | Execute__exc_items_set | 194267 | 266889 | ci/integration_tests/src/tests/cw_core_test.rs:134 |
| cw_core | Instantiate__exc_items_create_dao | 976909 | 1440759 | ci/integration_tests/src/helpers/helper.rs:87 |
| cw_core | Execute__exc_admin_msgs_pause_dao | 194393 | 267078 | ci/integration_tests/src/tests/cw_core_test.rs:74 |
| cw_core | Instantiate__exc_admin_msgs_create_dao | 975860 | 1439184 | ci/integration_tests/src/helpers/helper.rs:87 |
| cw_core | Instantiate__exc_admin_msgs_create_dao_with_admin | 976909 | 1440759 | ci/integration_tests/src/helpers/helper.rs:87 |
| cw_core | Instantiate__inst_dao_no_admin | 975860 | 1439184 | ci/integration_tests/src/helpers/helper.rs:87 |
| cw_core | Instantiate__exc_stake_create_dao | 975836 | 1439148 | ci/integration_tests/src/helpers/helper.rs:87 |
| cw_core | Instantiate__inst_admin_create_dao | 976909 | 1440759 | ci/integration_tests/src/helpers/helper.rs:87 |
| cw_core | Store__Store | 45256290 | 67859795 | ci/integration_tests/src/helpers/chain.rs:82 |
| cw20_base | Execute__exc_stake_stake_tokens | 234580 | 319226 | ci/integration_tests/src/tests/cw20_stake_test.rs:72 |
| cw20_base | Store__Store | 34725990 | 52064345 | ci/integration_tests/src/helpers/chain.rs:82 |
| cw20_stake | Store__Store | 29024865 | 43512986 | ci/integration_tests/src/helpers/chain.rs:82 |
| cw20_staked_balance_voting | Store__Store | 29469420 | 44179490 | ci/integration_tests/src/helpers/chain.rs:82 |
| cw4_group | Store__Store | 21882500 | 32799110 | ci/integration_tests/src/helpers/chain.rs:82 |
| cw4_voting | Store__Store | 21842460 | 32739050 | ci/integration_tests/src/helpers/chain.rs:82 |
| cw721_stake | Store__Store | 31174530 | 46737155 | ci/integration_tests/src/helpers/chain.rs:82 |
| cw_admin_factory | Store__Store | 16212880 | 24294680 | ci/integration_tests/src/helpers/chain.rs:82 |
| cw_named_groups | Store__Store | 19605940 | 29384270 | ci/integration_tests/src/helpers/chain.rs:82 |
| cw_names_registry | Store__Store | 21283000 | 31899860 | ci/integration_tests/src/helpers/chain.rs:82 |
| cw_native_staked_balance_voting | Store__Store | 24396550 | 36570185 | ci/integration_tests/src/helpers/chain.rs:82 |
| cw_proposal_multiple | Store__Store | 51298810 | 76923575 | ci/integration_tests/src/helpers/chain.rs:82 |
| cw_proposal_single | Store__Store | 51830000 | 77720360 | ci/integration_tests/src/helpers/chain.rs:82 |
| cw_token_swap | Store__Store | 19325330 | 28963355 | ci/integration_tests/src/helpers/chain.rs:82 |
| stake_cw20_external_rewards | Store__Store | 25509310 | 38239325 | ci/integration_tests/src/helpers/chain.rs:82 |
| stake_cw20_reward_distributor | Store__Store | 19374500 | 29037110 | ci/integration_tests/src/helpers/chain.rs:82 |
</details>
JakeHartnell: > This is a pretty straightforward change and I am ready to approve (do see my one comment though).
> 
> We may want to think (in the future, and when the spec is no longer a draft!) about how to read, validate, and display this standard nicely. It also occurs to me that we could auto-populate the JSON this standard calls for to a pretty great degree using the UI. We may want to add that to our roadmap once this standard becomes more mature.

Yes, the idea would be that we would auto-populate this in the UI.

Patch:
diff --git a/contracts/cw-core/examples/schema.rs b/contracts/cw-core/examples/schema.rs
index 582b4867..293e68f4 100644
--- a/contracts/cw-core/examples/schema.rs
+++ b/contracts/cw-core/examples/schema.rs
@@ -38,6 +38,7 @@ fn main() {
     // Auto TS code generation expects the query return type as QueryNameResponse
     // Here we map query responses to the correct name
     export_schema_with_title(&schema_for!(Option<Addr>), &out_dir, "AdminResponse");
+    export_schema_with_title(&schema_for!(Option<String>), &out_dir, "DaoURIResponse");
     export_schema_with_title(&schema_for!(Config), &out_dir, "ConfigResponse");
     export_schema_with_title(
         &schema_for!(Cw20BalanceResponse),
diff --git a/contracts/cw-core/schema/config_response.json b/contracts/cw-core/schema/config_response.json
index f609c4d9..8a458f87 100644
--- a/contracts/cw-core/schema/config_response.json
+++ b/contracts/cw-core/schema/config_response.json
@@ -18,6 +18,13 @@
       "description": "If true the contract will automatically add received cw721 tokens to its treasury.",
       "type": "boolean"
     },
+    "dao_uri": {
+      "description": "The URI for the DAO as defined by the DAOstar standard https://daostar.one/EIP",
+      "type": [
+        "string",
+        "null"
+      ]
+    },
     "description": {
       "description": "A description of the contract.",
       "type": "string"
diff --git a/contracts/cw-core/schema/dao_u_r_i_response.json b/contracts/cw-core/schema/dao_u_r_i_response.json
new file mode 100644
index 00000000..2b9566ad
--- /dev/null
+++ b/contracts/cw-core/schema/dao_u_r_i_response.json
@@ -0,0 +1,8 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "DaoURIResponse",
+  "type": [
+    "string",
+    "null"
+  ]
+}
diff --git a/contracts/cw-core/schema/dump_state_response.json b/contracts/cw-core/schema/dump_state_response.json
index 36e718d6..0fc6f328 100644
--- a/contracts/cw-core/schema/dump_state_response.json
+++ b/contracts/cw-core/schema/dump_state_response.json
@@ -103,6 +103,13 @@
           "description": "If true the contract will automatically add received cw721 tokens to its treasury.",
           "type": "boolean"
         },
+        "dao_uri": {
+          "description": "The URI for the DAO as defined by the DAOstar standard https://daostar.one/EIP",
+          "type": [
+            "string",
+            "null"
+          ]
+        },
         "description": {
           "description": "A description of the contract.",
           "type": "string"
diff --git a/contracts/cw-core/schema/execute_msg.json b/contracts/cw-core/schema/execute_msg.json
index f388e666..acbe1119 100644
--- a/contracts/cw-core/schema/execute_msg.json
+++ b/contracts/cw-core/schema/execute_msg.json
@@ -504,6 +504,13 @@
           "description": "If true the contract will automatically add received cw721 tokens to its treasury.",
           "type": "boolean"
         },
+        "dao_uri": {
+          "description": "The URI for the DAO as defined by the DAOstar standard https://daostar.one/EIP",
+          "type": [
+            "string",
+            "null"
+          ]
+        },
         "description": {
           "description": "A description of the contract.",
           "type": "string"
diff --git a/contracts/cw-core/schema/instantiate_msg.json b/contracts/cw-core/schema/instantiate_msg.json
index 10b76ac2..6e16cc29 100644
--- a/contracts/cw-core/schema/instantiate_msg.json
+++ b/contracts/cw-core/schema/instantiate_msg.json
@@ -26,6 +26,13 @@
       "description": "If true the contract will automatically add received cw721 tokens to its treasury.",
       "type": "boolean"
     },
+    "dao_uri": {
+      "description": "Implements the DAO Star standard: https://daostar.one/EIP",
+      "type": [
+        "string",
+        "null"
+      ]
+    },
     "description": {
       "description": "A description of the core contract.",
       "type": "string"
diff --git a/contracts/cw-core/schema/migrate_msg.json b/contracts/cw-core/schema/migrate_msg.json
index 81f306ae..6b9264c8 100644
--- a/contracts/cw-core/schema/migrate_msg.json
+++ b/contracts/cw-core/schema/migrate_msg.json
@@ -9,7 +9,15 @@
       ],
       "properties": {
         "from_v1": {
-          "type": "object"
+          "type": "object",
+          "properties": {
+            "dao_uri": {
+              "type": [
+                "string",
+                "null"
+              ]
+            }
+          }
         }
       },
       "additionalProperties": false
diff --git a/contracts/cw-core/schema/query_msg.json b/contracts/cw-core/schema/query_msg.json
index cf9325eb..f3fe0a75 100644
--- a/contracts/cw-core/schema/query_msg.json
+++ b/contracts/cw-core/schema/query_msg.json
@@ -304,6 +304,19 @@
       },
       "additionalProperties": false
     },
+    {
+      "description": "Implements the DAO Star standard: https://daostar.one/EIP",
+      "type": "object",
+      "required": [
+        "dao_u_r_i"
+      ],
+      "properties": {
+        "dao_u_r_i": {
+          "type": "object"
+        }
+      },
+      "additionalProperties": false
+    },
     {
       "type": "object",
       "required": [
diff --git a/contracts/cw-core/src/contract.rs b/contracts/cw-core/src/contract.rs
index dc110962..5799de6e 100644
--- a/contracts/cw-core/src/contract.rs
+++ b/contracts/cw-core/src/contract.rs
@@ -5,11 +5,13 @@ use cosmwasm_std::{
     Response, StdError, StdResult, SubMsg,
 };
 use cw2::{get_contract_version, set_contract_version};
-use cw_storage_plus::Map;
+use cw_storage_plus::{Item, Map};
 use cw_utils::{parse_reply_instantiate_data, Duration};
 
 use cw_core_interface::voting;
 use cw_paginate::{paginate_map, paginate_map_keys, paginate_map_values};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
 
 use crate::error::ContractError;
 use crate::msg::{
@@ -48,6 +50,7 @@ pub fn instantiate(
         image_url: msg.image_url,
         automatically_add_cw20s: msg.automatically_add_cw20s,
         automatically_add_cw721s: msg.automatically_add_cw721s,
+        dao_uri: msg.dao_uri,
     };
     CONFIG.save(deps.storage, &config)?;
 
@@ -566,6 +569,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
         QueryMsg::ListSubDaos { start_after, limit } => {
             query_list_sub_daos(deps, start_after, limit)
         }
+        QueryMsg::DaoURI {} => query_dao_uri(deps),
     }
 }
 
@@ -827,16 +831,34 @@ pub fn query_list_sub_daos(
     to_binary(&subdaos)
 }
 
+pub fn query_dao_uri(deps: Deps) -> StdResult<Binary> {
+    let config = CONFIG.load(deps.storage)?;
+    to_binary(&config.dao_uri)
+}
+
 #[cfg_attr(not(feature = "library"), entry_point)]
 pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
     set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
     match msg {
-        MigrateMsg::FromV1 {} => {
+        MigrateMsg::FromV1 { dao_uri } => {
+            // This config version is from commit
+            // e531c760a5d057329afd98d62567aaa4dca2c96f (v1.0.0) and code ID
+            // 432.
+            #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
+            struct V1Config {
+                pub name: String,
+                pub description: String,
+                pub image_url: Option<String>,
+                pub automatically_add_cw20s: bool,
+                pub automatically_add_cw721s: bool,
+            }
+
             let current_map: Map<Addr, Empty> = Map::new("proposal_modules");
             let current_keys = current_map
                 .keys(deps.storage, None, None, Order::Ascending)
                 .collect::<StdResult<Vec<Addr>>>()?;
 
+            // Update proposal modules to v2.
             current_keys
                 .into_iter()
                 .enumerate()
@@ -850,6 +872,22 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result<Response, Co
                     PROPOSAL_MODULES.save(deps.storage, address, proposal_module)?;
                     Ok(())
                 })?;
+
+            // Update config to have the V2 "dao_uri" field.
+            let config_item: Item<V1Config> = Item::new("config");
+            let v1_config: V1Config = config_item.load(deps.storage)?;
+            CONFIG.save(
+                deps.storage,
+                &Config {
+                    name: v1_config.name,
+                    description: v1_config.description,
+                    image_url: v1_config.image_url,
+                    automatically_add_cw20s: v1_config.automatically_add_cw20s,
+                    automatically_add_cw721s: v1_config.automatically_add_cw721s,
+                    dao_uri,
+                },
+            )?;
+
             Ok(Response::default())
         }
         MigrateMsg::FromCompatible {} => Ok(Response::default()),
diff --git a/contracts/cw-core/src/msg.rs b/contracts/cw-core/src/msg.rs
index e52e3c56..ac361b2a 100644
--- a/contracts/cw-core/src/msg.rs
+++ b/contracts/cw-core/src/msg.rs
@@ -76,6 +76,8 @@ pub struct InstantiateMsg {
     /// items map. The value is an enum that either uses an existing
     /// address or instantiates a new contract.
     pub initial_items: Option<Vec<InitialItem>>,
+    /// Implements the DAO Star standard: https://daostar.one/EIP
+    pub dao_uri: Option<String>,
 }
 
 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@@ -222,11 +224,13 @@ pub enum QueryMsg {
         start_after: Option<String>,
         limit: Option<u32>,
     },
+    /// Implements the DAO Star standard: https://daostar.one/EIP
+    DaoURI {},
 }
 
 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
 #[serde(rename_all = "snake_case")]
 pub enum MigrateMsg {
-    FromV1 {},
+    FromV1 { dao_uri: Option<String> },
     FromCompatible {},
 }
diff --git a/contracts/cw-core/src/state.rs b/contracts/cw-core/src/state.rs
index 2ec25d0a..32e612c1 100644
--- a/contracts/cw-core/src/state.rs
+++ b/contracts/cw-core/src/state.rs
@@ -20,6 +20,9 @@ pub struct Config {
     /// If true the contract will automatically add received cw721
     /// tokens to its treasury.
     pub automatically_add_cw721s: bool,
+    /// The URI for the DAO as defined by the DAOstar standard
+    /// https://daostar.one/EIP
+    pub dao_uri: Option<String>,
 }
 
 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
@@ -59,7 +62,7 @@ pub const ADMIN: Item<Addr> = Item::new("admin");
 pub const NOMINATED_ADMIN: Item<Addr> = Item::new("nominated_admin");
 
 /// The current configuration of the module.
-pub const CONFIG: Item<Config> = Item::new("config");
+pub const CONFIG: Item<Config> = Item::new("config_v2");
 
 /// The time the DAO will unpause. Here be dragons: this is not set if
 /// the DAO has never been paused.
diff --git a/typescript/contracts/cw-core/CwCoreContract.ts b/typescript/contracts/cw-core/CwCoreContract.ts
index d9697d8a..ac420a53 100644
--- a/typescript/contracts/cw-core/CwCoreContract.ts
+++ b/typescript/contracts/cw-core/CwCoreContract.ts
@@ -23,6 +23,7 @@ export type AdminResponse = Addr | null;
 export interface ConfigResponse {
   automatically_add_cw20s: boolean;
   automatically_add_cw721s: boolean;
+  dao_uri?: string | null;
   description: string;
   image_url?: string | null;
   name: string;
@@ -36,6 +37,7 @@ export interface Cw20BalancesResponse {
 }
 export type Cw20TokenListResponse = Addr[];
 export type Cw721TokenListResponse = Addr[];
+export type DaoURIResponse = string | null;
 export type Timestamp = Uint64;
 export type Uint64 = string;
 export type PauseInfoResponse = {
@@ -72,6 +74,7 @@ export interface DumpStateResponse {
 export interface Config {
   automatically_add_cw20s: boolean;
   automatically_add_cw721s: boolean;
+  dao_uri?: string | null;
   description: string;
   image_url?: string | null;
   name: string;
@@ -362,6 +365,7 @@ export interface InstantiateMsg {
   admin?: string | null;
   automatically_add_cw20s: boolean;
   automatically_add_cw721s: boolean;
+  dao_uri?: string | null;
   description: string;
   image_url?: string | null;
   initial_items?: InitialItem[] | null;
@@ -379,6 +383,7 @@ export type ListItemsResponse = string[];
 export type ListSubDaosResponse = SubDao[];
 export type MigrateMsg = {
   from_v1: {
+    dao_uri?: string | null;
     [k: string]: unknown;
   };
 } | {
@@ -458,6 +463,10 @@ export type QueryMsg = {
     start_after?: string | null;
     [k: string]: unknown;
   };
+} | {
+  dao_u_r_i: {
+    [k: string]: unknown;
+  };
 } | {
   voting_power_at_height: {
     address: string;
@@ -547,6 +556,7 @@ export interface CwCoreReadOnlyInterface {
     limit?: number;
     startAfter?: string;
   }) => Promise<ListSubDaosResponse>;
+  daoURI: () => Promise<DaoURIResponse>;
   votingPowerAtHeight: ({
     address,
     height
@@ -582,6 +592,7 @@ export class CwCoreQueryClient implements CwCoreReadOnlyInterface {
     this.pauseInfo = this.pauseInfo.bind(this);
     this.votingModule = this.votingModule.bind(this);
     this.listSubDaos = this.listSubDaos.bind(this);
+    this.daoURI = this.daoURI.bind(this);
     this.votingPowerAtHeight = this.votingPowerAtHeight.bind(this);
     this.totalPowerAtHeight = this.totalPowerAtHeight.bind(this);
     this.info = this.info.bind(this);
@@ -726,6 +737,11 @@ export class CwCoreQueryClient implements CwCoreReadOnlyInterface {
       }
     });
   };
+  daoURI = async (): Promise<DaoURIResponse> => {
+    return this.client.queryContractSmart(this.contractAddress, {
+      dao_u_r_i: {}
+    });
+  };
   votingPowerAtHeight = async ({
     address,
     height


Test patch:
diff --git a/ci/integration_tests/src/helpers/helper.rs b/ci/integration_tests/src/helpers/helper.rs
index ece3a0a6..0b09a6b2 100644
--- a/ci/integration_tests/src/helpers/helper.rs
+++ b/ci/integration_tests/src/helpers/helper.rs
@@ -25,6 +25,7 @@ pub fn create_dao(
     user_addr: String,
 ) -> Result<DaoState> {
     let msg = cw_core::msg::InstantiateMsg {
+        dao_uri: None,
         admin,
         name: "DAO DAO".to_string(),
         description: "A DAO that makes DAO tooling".to_string(),
diff --git a/ci/integration_tests/src/tests/cw_core_test.rs b/ci/integration_tests/src/tests/cw_core_test.rs
index 0aed4e33..c217c743 100644
--- a/ci/integration_tests/src/tests/cw_core_test.rs
+++ b/ci/integration_tests/src/tests/cw_core_test.rs
@@ -213,6 +213,7 @@ fn instantiate_with_no_admin(chain: &mut Chain) {
     assert_eq!(
         dao.state.config,
         cw_core::state::Config {
+            dao_uri: None,
             name: "DAO DAO".to_string(),
             description: "A DAO that makes DAO tooling".to_string(),
             image_url: None,
@@ -243,6 +244,7 @@ fn instantiate_with_admin(chain: &mut Chain) {
     assert_eq!(
         dao.state.config,
         cw_core::state::Config {
+            dao_uri: None,
             name: "DAO DAO".to_string(),
             description: "A DAO that makes DAO tooling".to_string(),
             image_url: None,
diff --git a/contracts/cw-admin-factory/src/tests.rs b/contracts/cw-admin-factory/src/tests.rs
index ca0e505e..429975a0 100644
--- a/contracts/cw-admin-factory/src/tests.rs
+++ b/contracts/cw-admin-factory/src/tests.rs
@@ -73,6 +73,7 @@ pub fn test_set_admin() {
     // Instantiate core contract using factory.
     let cw_core_code_id = app.store_code(cw_core_contract());
     let instantiate_core = cw_core::msg::InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs.".to_string(),
diff --git a/contracts/cw-core/src/tests.rs b/contracts/cw-core/src/tests.rs
index 09fab970..6c07d5af 100644
--- a/contracts/cw-core/src/tests.rs
+++ b/contracts/cw-core/src/tests.rs
@@ -6,8 +6,10 @@ use cosmwasm_std::{
 use cw2::ContractVersion;
 use cw_core_interface::voting::VotingPowerAtHeightResponse;
 use cw_multi_test::{App, Contract, ContractWrapper, Executor};
-use cw_storage_plus::Map;
+use cw_storage_plus::{Item, Map};
 use cw_utils::{Duration, Expiration};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
 
 use crate::{
     contract::{derive_proposal_module_prefix, migrate, CONTRACT_NAME, CONTRACT_VERSION},
@@ -98,6 +100,7 @@ fn test_instantiate_with_n_gov_modules(n: usize) {
         marketing: None,
     };
     let instantiate = InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs.".to_string(),
@@ -130,6 +133,7 @@ fn test_instantiate_with_n_gov_modules(n: usize) {
     assert_eq!(
         state.config,
         Config {
+            dao_uri: None,
             name: "DAO DAO".to_string(),
             description: "A DAO that builds DAOs.".to_string(),
             image_url: None,
@@ -207,6 +211,7 @@ makes wickedness."
     });
 
     let instantiate = InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs.".to_string(),
@@ -236,6 +241,7 @@ fn test_update_config() {
     };
 
     let gov_instantiate = InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs.".to_string(),
@@ -287,6 +293,7 @@ fn test_update_config() {
         image_url: Some("https://moonphase.is/image.svg".to_string()),
         automatically_add_cw20s: false,
         automatically_add_cw721s: true,
+        dao_uri: Some("https://daostar.one/EIP".to_string()),
     };
 
     app.execute_contract(
@@ -309,10 +316,16 @@ fn test_update_config() {
 
     let config: Config = app
         .wrap()
-        .query_wasm_smart(gov_addr, &QueryMsg::Config {})
+        .query_wasm_smart(gov_addr.clone(), &QueryMsg::Config {})
         .unwrap();
 
-    assert_eq!(expected_config, config)
+    assert_eq!(expected_config, config);
+
+    let dao_uri: Option<String> = app
+        .wrap()
+        .query_wasm_smart(gov_addr, &QueryMsg::DaoURI {})
+        .unwrap();
+    assert_eq!(dao_uri, expected_config.dao_uri);
 }
 
 fn test_swap_governance(swaps: Vec<(u32, u32)>) {
@@ -325,6 +338,7 @@ fn test_swap_governance(swaps: Vec<(u32, u32)>) {
     };
 
     let gov_instantiate = InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs.".to_string(),
@@ -476,6 +490,7 @@ fn test_removed_modules_can_not_execute() {
     };
 
     let gov_instantiate = InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs.".to_string(),
@@ -618,6 +633,7 @@ fn test_module_already_disabled() {
     };
 
     let gov_instantiate = InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs.".to_string(),
@@ -716,6 +732,7 @@ fn test_swap_voting_module() {
     };
 
     let gov_instantiate = InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs.".to_string(),
@@ -818,6 +835,7 @@ fn test_permissions() {
     };
 
     let gov_instantiate = InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs.".to_string(),
@@ -877,6 +895,7 @@ fn test_permissions() {
         gov_addr,
         ExecuteMsg::UpdateConfig {
             config: Config {
+                dao_uri: None,
                 name: "Evil config.".to_string(),
                 description: "👿".to_string(),
                 image_url: None,
@@ -913,6 +932,7 @@ fn do_standard_instantiate(auto_add: bool, admin: Option<String>) -> (Addr, App)
     };
 
     let gov_instantiate = InstantiateMsg {
+        dao_uri: None,
         admin,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs.".to_string(),
@@ -1510,6 +1530,7 @@ fn test_list_items() {
     };
 
     let gov_instantiate = InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs.".to_string(),
@@ -1626,6 +1647,7 @@ fn test_instantiate_with_items() {
     };
 
     let gov_instantiate = InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs.".to_string(),
@@ -2163,6 +2185,7 @@ fn test_pause() {
         core_addr.clone(),
         &ExecuteMsg::UpdateConfig {
             config: Config {
+                dao_uri: None,
                 name: "The Empire Strikes Back".to_string(),
                 description: "haha lol we have pwned your DAO".to_string(),
                 image_url: None,
@@ -2238,6 +2261,7 @@ fn test_pause() {
             core_addr.clone(),
             &ExecuteMsg::UpdateConfig {
                 config: Config {
+                    dao_uri: None,
                     name: "The Empire Strikes Back Again".to_string(),
                     description: "haha lol we have pwned your DAO again".to_string(),
                     image_url: None,
@@ -2382,6 +2406,9 @@ fn test_dump_state_proposal_modules() {
     assert_eq!(all_state.proposal_modules[0], proposal_module);
 }
 
+// Note that this isn't actually testing that we are migrating from the previous version since
+// with multitest contract instantiation we can't manipulate storage to the previous version of state before invoking migrate. So if anything,
+// this just tests the idempotency of migrate.
 #[test]
 fn test_migrate_from_compatible() {
     let mut app = App::default();
@@ -2410,6 +2437,7 @@ fn test_migrate_from_compatible() {
 
     // Instantiate the core module with an admin to do migrations.
     let gov_instantiate = InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs.".to_string(),
@@ -2465,119 +2493,66 @@ fn test_migrate_from_compatible() {
     assert_eq!(new_state, state);
 }
 
-#[test]
-fn test_migrate_from_beta() {
-    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 1".to_string(),
-            },
-            ModuleInstantiateInfo {
-                code_id: govmod_id,
-                msg: to_binary(&govmod_instantiate).unwrap(),
-                admin: Admin::CoreContract {},
-                label: "governance module 2".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();
-
-    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::FromV1 {}).unwrap(),
-        }),
-    )
-    .unwrap();
-
-    let new_state: DumpStateResponse = app
-        .wrap()
-        .query_wasm_smart(core_addr, &QueryMsg::DumpState {})
-        .unwrap();
-
-    let proposal_modules = new_state.proposal_modules;
-    assert_eq!(2, proposal_modules.len());
-    for (idx, module) in proposal_modules.iter().enumerate() {
-        let prefix = derive_proposal_module_prefix(idx).unwrap();
-        assert_eq!(prefix, module.prefix);
-        assert_eq!(ProposalModuleStatus::Enabled, module.status);
-    }
-}
-
 #[test]
 fn test_migrate_mock() {
     let mut deps = mock_dependencies();
-    let msg = MigrateMsg::FromV1 {};
+    let dao_uri: String = "/dao/uri".to_string();
+    let msg = MigrateMsg::FromV1 {
+        dao_uri: Some(dao_uri.clone()),
+    };
     let env = mock_env();
 
     // Write to storage in old proposal module format
-    let key = Addr::unchecked("addr");
+    let proposal_modules_key = Addr::unchecked("addr");
     let old_map: Map<Addr, Empty> = Map::new("proposal_modules");
-    let path = old_map.key(key.clone());
+    let path = old_map.key(proposal_modules_key.clone());
     deps.storage.set(&path, &to_binary(&Empty {}).unwrap());
 
-    // Migrate to new data format
+    // Write to storage in old config format
+    #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
+    struct V1Config {
+        pub name: String,
+        pub description: String,
+        pub image_url: Option<String>,
+        pub automatically_add_cw20s: bool,
+        pub automatically_add_cw721s: bool,
+    }
+
+    let v1_config = V1Config {
+        name: "core dao".to_string(),
+        description: "a dao".to_string(),
+        image_url: None,
+        automatically_add_cw20s: false,
+        automatically_add_cw721s: false,
+    };
+
+    let config_item: Item<V1Config> = Item::new("config");
+    config_item.save(&mut deps.storage, &v1_config).unwrap();
+
+    // Migrate to v2
     migrate(deps.as_mut(), env, msg).unwrap();
 
-    let new_path = PROPOSAL_MODULES.key(key);
+    let new_path = PROPOSAL_MODULES.key(proposal_modules_key);
     let prop_module_bytes = deps.storage.get(&new_path).unwrap();
     let module: ProposalModule = from_slice(&prop_module_bytes).unwrap();
     assert_eq!(module.address, Addr::unchecked("addr"));
     assert_eq!(module.prefix, derive_proposal_module_prefix(0).unwrap());
     assert_eq!(module.status, ProposalModuleStatus::Enabled {});
+
+    let v2_config_item: Item<Config> = Item::new("config_v2");
+    let v2_config = v2_config_item.load(&deps.storage).unwrap();
+    assert_eq!(v2_config.dao_uri, Some(dao_uri));
+    assert_eq!(v2_config.name, v1_config.name);
+    assert_eq!(v2_config.description, v1_config.description);
+    assert_eq!(v2_config.image_url, v1_config.image_url);
+    assert_eq!(
+        v2_config.automatically_add_cw20s,
+        v1_config.automatically_add_cw20s
+    );
+    assert_eq!(
+        v2_config.automatically_add_cw721s,
+        v1_config.automatically_add_cw721s
+    )
 }
 
 #[test]
@@ -2623,6 +2598,7 @@ fn test_module_prefixes() {
     };
 
     let gov_instantiate = InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs.".to_string(),
@@ -2849,6 +2825,7 @@ fn test_created_timestamp_set() {
     let timestamp = Timestamp::from_seconds(300_000_000);
     app.update_block(|mut block| block.time = timestamp);
     let instantiate = InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs.".to_string(),
diff --git a/contracts/cw-proposal-single/src/staking_tests.rs b/contracts/cw-proposal-single/src/staking_tests.rs
index 420becdf..08a4ba6a 100644
--- a/contracts/cw-proposal-single/src/staking_tests.rs
+++ b/contracts/cw-proposal-single/src/staking_tests.rs
@@ -69,6 +69,7 @@ fn instantiate_with_staked_balances_voting() {
     let staked_balances_voting_id = app.store_code(staked_balances_voting());
 
     let instantiate_core = cw_core::msg::InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs".to_string(),
@@ -140,6 +141,7 @@ fn instantiate_with_staked_balances_voting() {
     assert_eq!(
         state.config,
         cw_core::state::Config {
+            dao_uri: None,
             name: "DAO DAO".to_string(),
             description: "A DAO that builds DAOs".to_string(),
             image_url: None,
diff --git a/contracts/cw-proposal-single/src/tests.rs b/contracts/cw-proposal-single/src/tests.rs
index 0791e74c..1fd179de 100644
--- a/contracts/cw-proposal-single/src/tests.rs
+++ b/contracts/cw-proposal-single/src/tests.rs
@@ -236,6 +236,7 @@ fn instantiate_with_staked_cw721_governance(
         .unwrap();
 
     let instantiate_core = cw_core::msg::InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs".to_string(),
@@ -347,6 +348,7 @@ fn instantiate_with_native_staked_balances_governance(
     let core_contract_id = app.store_code(cw_gov_contract());
 
     let instantiate_core = cw_core::msg::InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs".to_string(),
@@ -452,6 +454,7 @@ fn instantiate_with_staked_balances_governance(
     let core_contract_id = app.store_code(cw_gov_contract());
 
     let instantiate_core = cw_core::msg::InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs".to_string(),
@@ -561,6 +564,7 @@ fn instantiate_with_staking_active_threshold(
     });
 
     let governance_instantiate = cw_core::msg::InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs".to_string(),
@@ -641,6 +645,7 @@ fn instantiate_with_cw4_groups_governance(
     };
 
     let governance_instantiate = cw_core::msg::InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs".to_string(),
@@ -717,6 +722,7 @@ fn instantiate_with_cw20_balances_governance(
     };
 
     let governance_instantiate = cw_core::msg::InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs".to_string(),
@@ -3800,6 +3806,9 @@ fn test_large_absolute_count_threshold() {
     );
 }
 
+// Note that this isn't actually testing that we are migrating from the previous version since
+// with multitest contract instantiation we can't manipulate storage to the previous version of state before invoking migrate. So if anything,
+// this just tests the idempotency of migrate.
 #[test]
 fn test_migrate_from_compatible() {
     let mut app = App::default();
diff --git a/debug/proposal-hooks-counter/src/tests.rs b/debug/proposal-hooks-counter/src/tests.rs
index f316b059..8d43e13a 100644
--- a/debug/proposal-hooks-counter/src/tests.rs
+++ b/debug/proposal-hooks-counter/src/tests.rs
@@ -92,6 +92,7 @@ fn instantiate_with_default_governance(
     });
 
     let governance_instantiate = cw_core::msg::InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs".to_string(),
diff --git a/packages/testing/src/helpers.rs b/packages/testing/src/helpers.rs
index 1a639441..1af875bc 100644
--- a/packages/testing/src/helpers.rs
+++ b/packages/testing/src/helpers.rs
@@ -41,6 +41,7 @@ pub fn instantiate_with_cw20_balances_governance(
     };
 
     let governance_instantiate = cw_core::msg::InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs".to_string(),
@@ -119,6 +120,7 @@ pub fn instantiate_with_staked_balances_governance(
     let core_contract_id = app.store_code(cw_gov_contract());
 
     let instantiate_core = cw_core::msg::InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs".to_string(),
@@ -228,6 +230,7 @@ pub fn instantiate_with_staking_active_threshold(
     });
 
     let governance_instantiate = cw_core::msg::InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs".to_string(),
@@ -308,6 +311,7 @@ pub fn instantiate_with_cw4_groups_governance(
     };
 
     let governance_instantiate = cw_core::msg::InstantiateMsg {
+        dao_uri: None,
         admin: None,
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs".to_string(),