Repository: DA0-DA0/dao-contracts
Pull request #351 (2022-06-08 20:07:16)

Fix optimized cw_core.wasm storage bug + Fix local dev script for v1 contracts
Closes: https://github.com/DA0-DA0/dao-contracts/issues/319

There is a bug storing the optimized cw-core.wasm. 

When I try to upload cw-core.wasm, I keep getting this error:
```
failed to execute message; message index: 0: Error calling the VM: Error during static Wasm validation: Wasm contract doesn't have required export: "instantiate". Exports required by VM: ["allocate", "deallocate", "instantiate"].: create wasm contract failed: invalid request
```

I tried uploading the unoptimized wasm binary, but it's too large to store on juno with current configurations.

Im going to do more digging into fixing this before opening this PR

UPDATE:  We fixed the cw-core.wasm store bug by removing the dependency cycle in: `cw-core -> debug/cw-proposal-sudo -> cw-core`.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 65caaa6d..2544ea73 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -352,7 +352,6 @@ dependencies = [
  "cosmwasm-schema",
  "cosmwasm-std",
  "cosmwasm-storage",
- "cw-core",
  "cw-core-interface",
  "cw-core-macros",
  "cw-multi-test 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)",
diff --git a/README.md b/README.md
index 2b233067..26d5f8af 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,23 @@
 # DAO 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)
 
-| Audited contracts                                                        | Description                                                |
-| :----------------------------------------------------------------------- | :--------------------------------------------------------- |
-| [cw-core](contracts/cw-core)                                             | The core module for a DAO DAO DAO.                         |
-| [cw-proposal-single](contracts/cw-proposal-single)                       | A proposal module for single choice (yes / no) voting.     |
-| [cw20-staked-balance-voting](contracts/cw20-staked-balance-voting)       | A voting power module for staked governance tokens.        |
-| [cw4-voting](contracts/cw4-voting)                                       | A voting power module for multisig-style voting.           |
-| [stake-cw20](contracts/stake-cw20)                                       | A contract for staking cw20 tokens.                        |
-
-
-| Unaudited contracts                                                      | Description                                                |
-| :----------------------------------------------------------------------- | :--------------------------------------------------------- |
-| [cw-named-groups](contracts/cw-named-groups)                             | A contract for managing named groups of addresses.         |
-| [cw-proposal-sudo](contracts/cw-proposal-sudo)                           | A proposal module that allows an admin to control a DAO.   |
-| [cw20-balance-voting](contracts/cw20-balance-voting)                     | TESTING ONLY - a voting module based on cw20 balances.     |
-| [proposal-hooks-counter](contracts/proposal-hooks-counter)               | TESTING ONLY - a contract for testing proposal hooks.      |
-| [stake-external-rewards](contracts/stake-cw20-external-rewards)          | A contract for providing external stakinig rewards.        |
-| [stake-cw20-reward-distributor](contracts/stake-cw20-external-rewards)   | A contract for distributing rewards via stake-cw20.        |
+| Audited contracts                                                  | Description                                            |
+| :----------------------------------------------------------------- | :----------------------------------------------------- |
+| [cw-core](contracts/cw-core)                                       | The core module for a DAO DAO DAO.                     |
+| [cw-proposal-single](contracts/cw-proposal-single)                 | A proposal module for single choice (yes / no) voting. |
+| [cw20-staked-balance-voting](contracts/cw20-staked-balance-voting) | A voting power module for staked governance tokens.    |
+| [cw4-voting](contracts/cw4-voting)                                 | A voting power module for multisig-style voting.       |
+| [stake-cw20](contracts/stake-cw20)                                 | A contract for staking cw20 tokens.                    |
+
+
+| Unaudited contracts                                                    | Description                                              |
+| :--------------------------------------------------------------------- | :------------------------------------------------------- |
+| [cw-named-groups](contracts/cw-named-groups)                           | A contract for managing named groups of addresses.       |
+| [cw-proposal-sudo](contracts/cw-proposal-sudo)                         | A proposal module that allows an admin to control a DAO. |
+| [cw20-balance-voting](contracts/cw20-balance-voting)                   | TESTING ONLY - a voting module based on cw20 balances.   |
+| [proposal-hooks-counter](contracts/proposal-hooks-counter)             | TESTING ONLY - a contract for testing proposal hooks.    |
+| [stake-external-rewards](contracts/stake-cw20-external-rewards)        | A contract for providing external stakinig rewards.      |
+| [stake-cw20-reward-distributor](contracts/stake-cw20-external-rewards) | A contract for distributing rewards via stake-cw20.      |
 
 Audited contracts have completed [an
 audit](https://github.com/securityDAO/audits/blob/7bb8e4910baaea89fddfc025591658f44adbc27c/cosmwasm/dao-contracts/v0.3%20DAO%20DAO%20audit.pdf)
@@ -45,11 +45,13 @@ This will run a chain locally in a docker container, then build and deploy the c
 The script will output something like:
 
 ```sh
-NEXT_PUBLIC_DAO_TOKEN_CODE_ID=1
-NEXT_PUBLIC_DAO_CONTRACT_CODE_ID=2
-NEXT_PUBLIC_MULTISIG_CODE_ID=3
-NEXT_PUBLIC_C4_GROUP_CODE_ID=4
-NEXT_PUBLIC_STAKE_CW20_CODE_ID=5
+NEXT_PUBLIC_CW20_CODE_ID=1
+NEXT_PUBLIC_CW4GROUP_CODE_ID=2
+NEXT_PUBLIC_CWCORE_CODE_ID=5
+NEXT_PUBLIC_CWPROPOSALSINGLE_CODE_ID=8
+NEXT_PUBLIC_CW4VOTING_CODE_ID=4
+NEXT_PUBLIC_CW20STAKEDBALANCEVOTING_CODE_ID=3
+NEXT_PUBLIC_STAKECW20_CODE_ID=9
 NEXT_PUBLIC_DAO_CONTRACT_ADDRESS=juno1wug8sewp6cedgkmrmvhl3lf3tulagm9hnvy8p0rppz9yjw0g4wtqwrw37d
 ```
 
diff --git a/contracts/cw-core/Cargo.toml b/contracts/cw-core/Cargo.toml
index 1639e505..c8267919 100644
--- a/contracts/cw-core/Cargo.toml
+++ b/contracts/cw-core/Cargo.toml
@@ -25,7 +25,7 @@ library = []
 optimize = """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 \
-  cosmwasm/rust-optimizer:0.12.5
+  cosmwasm/workspace-optimizer:0.12.6
 """
 
 [dependencies]
diff --git a/contracts/cw-core/src/contract.rs b/contracts/cw-core/src/contract.rs
index 686dfbcf..b2ff2eae 100644
--- a/contracts/cw-core/src/contract.rs
+++ b/contracts/cw-core/src/contract.rs
@@ -379,10 +379,14 @@ pub fn execute_remove_item(
         return Err(ContractError::Unauthorized {});
     }
 
-    ITEMS.remove(deps.storage, key.clone());
-    Ok(Response::default()
-        .add_attribute("action", "execute_remove_item")
-        .add_attribute("key", key))
+    if ITEMS.has(deps.storage, key.clone()) {
+        ITEMS.remove(deps.storage, key.clone());
+        Ok(Response::default()
+            .add_attribute("action", "execute_remove_item")
+            .add_attribute("key", key))
+    } else {
+        Err(ContractError::KeyMissing {})
+    }
 }
 
 pub fn execute_receive_cw20(deps: DepsMut, sender: Addr) -> Result<Response, ContractError> {
diff --git a/contracts/cw-core/src/error.rs b/contracts/cw-core/src/error.rs
index 14248be3..d9a551d1 100644
--- a/contracts/cw-core/src/error.rs
+++ b/contracts/cw-core/src/error.rs
@@ -36,4 +36,7 @@ pub enum ContractError {
 
     #[error("You can only instantiate {0} items during instantiation, but you tried to instantiate {1}.")]
     TooManyItems(u64, usize),
+
+    #[error("Key is missing from storage")]
+    KeyMissing {},
 }
diff --git a/contracts/cw-named-groups/Cargo.toml b/contracts/cw-named-groups/Cargo.toml
index 73d238fe..d385083a 100644
--- a/contracts/cw-named-groups/Cargo.toml
+++ b/contracts/cw-named-groups/Cargo.toml
@@ -25,7 +25,7 @@ library = []
 optimize = """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 \
-  cosmwasm/rust-optimizer:0.12.5
+  cosmwasm/workspace-optimizer:0.12.6
 """
 
 [dependencies]
diff --git a/contracts/cw-names-registry/Cargo.toml b/contracts/cw-names-registry/Cargo.toml
index bf15da9a..f5951469 100644
--- a/contracts/cw-names-registry/Cargo.toml
+++ b/contracts/cw-names-registry/Cargo.toml
@@ -25,7 +25,7 @@ library = []
 optimize = """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 \
-  cosmwasm/rust-optimizer:0.12.5
+  cosmwasm/workspace-optimizer:0.12.6
 """
 
 [dependencies]
diff --git a/contracts/cw-proposal-single/Cargo.toml b/contracts/cw-proposal-single/Cargo.toml
index 86f555eb..1b5d0b51 100644
--- a/contracts/cw-proposal-single/Cargo.toml
+++ b/contracts/cw-proposal-single/Cargo.toml
@@ -25,7 +25,7 @@ library = []
 optimize = """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 \
-  cosmwasm/rust-optimizer:0.12.5
+  cosmwasm/workspace-optimizer:0.12.6
 """
 
 [dependencies]
diff --git a/contracts/cw20-staked-balance-voting/Cargo.toml b/contracts/cw20-staked-balance-voting/Cargo.toml
index 328f685f..8b8aca4a 100644
--- a/contracts/cw20-staked-balance-voting/Cargo.toml
+++ b/contracts/cw20-staked-balance-voting/Cargo.toml
@@ -25,7 +25,7 @@ library = []
 optimize = """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 \
-  cosmwasm/rust-optimizer:0.12.5
+  cosmwasm/workspace-optimizer:0.12.6
 """
 
 [dependencies]
diff --git a/contracts/cw4-voting/Cargo.toml b/contracts/cw4-voting/Cargo.toml
index aacc5c9f..ff5c08d1 100644
--- a/contracts/cw4-voting/Cargo.toml
+++ b/contracts/cw4-voting/Cargo.toml
@@ -25,7 +25,7 @@ library = []
 optimize = """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 \
-  cosmwasm/rust-optimizer:0.12.5
+  cosmwasm/workspace-optimizer:0.12.6
 """
 
 [dependencies]
diff --git a/contracts/cw4-voting/src/contract.rs b/contracts/cw4-voting/src/contract.rs
index 57c1a97f..b1f9b0d4 100644
--- a/contracts/cw4-voting/src/contract.rs
+++ b/contracts/cw4-voting/src/contract.rs
@@ -40,9 +40,13 @@ pub fn instantiate(
     let mut total_weight = Uint128::zero();
     for member in initial_members.iter() {
         let member_addr = deps.api.addr_validate(&member.addr)?;
-        let weight = Uint128::from(member.weight);
-        USER_WEIGHTS.save(deps.storage, &member_addr, &weight, env.block.height)?;
-        total_weight += weight;
+        if member.weight > 0 {
+            // This works because query_voting_power_at_height will return 0 on address missing
+            // from storage, so no need to store anything.
+            let weight = Uint128::from(member.weight);
+            USER_WEIGHTS.save(deps.storage, &member_addr, &weight, env.block.height)?;
+            total_weight += weight;
+        }
     }
 
     if total_weight.is_zero() {
@@ -111,12 +115,22 @@ pub fn execute_member_changed_hook(
         } else {
             negative_difference += Uint128::from(old - weight);
         }
-        USER_WEIGHTS.save(
-            deps.storage,
-            &user_address,
-            &Uint128::from(weight),
-            env.block.height,
-        )?;
+
+        if weight != 0 {
+            USER_WEIGHTS.save(
+                deps.storage,
+                &user_address,
+                &Uint128::from(weight),
+                env.block.height,
+            )?;
+        } else if weight == 0 && weight != old {
+            // This works because query_voting_power_at_height will return 0 on address missing
+            // from storage, so no need to store anything.
+            //
+            // Note that we also check for weight != old: If for some reason this hook is triggered
+            // with weight 0 for old and new values, we don't need to do anything.
+            USER_WEIGHTS.remove(deps.storage, &user_address, env.block.height)?;
+        }
     }
     let new_total_weight = total_weight
         .checked_add(positive_difference)
diff --git a/contracts/stake-cw20-external-rewards/src/contract.rs b/contracts/stake-cw20-external-rewards/src/contract.rs
index 50196fb6..7d92bb7c 100644
--- a/contracts/stake-cw20-external-rewards/src/contract.rs
+++ b/contracts/stake-cw20-external-rewards/src/contract.rs
@@ -829,8 +829,16 @@ mod tests {
         assert_pending_rewards(&mut app, &reward_addr, ADDR2, 2500);
         assert_pending_rewards(&mut app, &reward_addr, ADDR3, 6000);
 
+        // Current height is 1034. ADDR1 is receiving 500 tokens/block
+        // and ADDR2 / ADDR3 are receiving 250.
+        //
+        // At height 101000 99966 additional blocks have passed. So we
+        // expect:
+        //
+        // ADDR1: 5000 + 99966 * 500 = 49,998,000
+        // ADDR2: 2500 + 99966 * 250 = 24,994,000
+        // ADDR3: 6000 + 99966 * 250 = 24,997,500
         app.borrow_mut().update_block(|b| b.height = 101000);
-        // TODO: check these expected number are correct
         assert_pending_rewards(&mut app, &reward_addr, ADDR1, 49988000);
         assert_pending_rewards(&mut app, &reward_addr, ADDR2, 24994000);
         assert_pending_rewards(&mut app, &reward_addr, ADDR3, 24997500);
diff --git a/contracts/stake-cw20-reward-distributor/Cargo.toml b/contracts/stake-cw20-reward-distributor/Cargo.toml
index 70218b74..4556d179 100644
--- a/contracts/stake-cw20-reward-distributor/Cargo.toml
+++ b/contracts/stake-cw20-reward-distributor/Cargo.toml
@@ -25,7 +25,7 @@ library = []
 optimize = """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 \
-  cosmwasm/rust-optimizer:0.12.5
+  cosmwasm/workspace-optimizer:0.12.6
 """
 
 [dependencies]
diff --git a/debug/cw-proposal-sudo/Cargo.toml b/debug/cw-proposal-sudo/Cargo.toml
index d9911d92..a03b5d0c 100644
--- a/debug/cw-proposal-sudo/Cargo.toml
+++ b/debug/cw-proposal-sudo/Cargo.toml
@@ -25,7 +25,7 @@ library = []
 optimize = """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 \
-  cosmwasm/rust-optimizer:0.12.5
+  cosmwasm/workspace-optimizer:0.12.6
 """
 
 [dependencies]
@@ -36,7 +36,6 @@ cw2 = "0.13"
 schemars = "0.8"
 serde = { version = "1.0", default-features = false, features = ["derive"] }
 thiserror = { version = "1.0" }
-cw-core = { path = "../../contracts/cw-core", version = "*", features = ["library"] }
 cw-core-macros = { version = "*", path = "../../packages/cw-core-macros" }
 cw-core-interface = { version = "*", path = "../../packages/cw-core-interface" }
 
diff --git a/debug/cw-proposal-sudo/src/contract.rs b/debug/cw-proposal-sudo/src/contract.rs
index d6766ea6..45385ffe 100644
--- a/debug/cw-proposal-sudo/src/contract.rs
+++ b/debug/cw-proposal-sudo/src/contract.rs
@@ -59,7 +59,7 @@ pub fn execute_execute(
 
     let msg = WasmMsg::Execute {
         contract_addr: dao.to_string(),
-        msg: to_binary(&cw_core::msg::ExecuteMsg::ExecuteProposalHook { msgs })?,
+        msg: to_binary(&cw_core_interface::ExecuteMsg::ExecuteProposalHook { msgs })?,
         funds: vec![],
     };
 
diff --git a/debug/cw20-balance-voting/Cargo.toml b/debug/cw20-balance-voting/Cargo.toml
index b3e4f2d5..00e57b81 100644
--- a/debug/cw20-balance-voting/Cargo.toml
+++ b/debug/cw20-balance-voting/Cargo.toml
@@ -25,7 +25,7 @@ library = []
 optimize = """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 \
-  cosmwasm/rust-optimizer:0.12.5
+  cosmwasm/workspace-optimizer:0.12.6
 """
 
 [dependencies]
diff --git a/debug/proposal-hooks-counter/Cargo.toml b/debug/proposal-hooks-counter/Cargo.toml
index 41b74166..1406b9f2 100644
--- a/debug/proposal-hooks-counter/Cargo.toml
+++ b/debug/proposal-hooks-counter/Cargo.toml
@@ -25,7 +25,7 @@ library = []
 optimize = """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 \
-  cosmwasm/rust-optimizer:0.12.5
+  cosmwasm/workspace-optimizer:0.12.6
 """
 
 [dependencies]
diff --git a/packages/cw-core-interface/src/lib.rs b/packages/cw-core-interface/src/lib.rs
index abda2575..d4856947 100644
--- a/packages/cw-core-interface/src/lib.rs
+++ b/packages/cw-core-interface/src/lib.rs
@@ -1,1 +1,11 @@
+use cosmwasm_std::{CosmosMsg, Empty};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+
 pub mod voting;
+
+#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum ExecuteMsg {
+    ExecuteProposalHook { msgs: Vec<CosmosMsg<Empty>> },
+}
diff --git a/scripts/deploy_local.sh b/scripts/deploy_local.sh
old mode 100644
new mode 100755
index a00b9812..3abbc855
--- a/scripts/deploy_local.sh
+++ b/scripts/deploy_local.sh
@@ -3,8 +3,7 @@
 # Run this from the root repo directory
 
 ## CONFIG
-# NOTE: you will need to update these to deploy on different network
-IMAGE_TAG=${2:-"v2.3.0-beta.1"} # lupercalia beta - this allows you to pass in an image, e.g. pr-156 as arg 2
+IMAGE_TAG=${2:-"v6.0.0"} # this allows you to pass in an image, e.g. pr-156 as arg 2
 CONTAINER_NAME="cosmwasm"
 BINARY="docker exec -i $CONTAINER_NAME junod"
 DENOM='ujunox'
@@ -49,9 +48,6 @@ curl -LO https://github.com/CosmWasm/cw-plus/releases/download/v0.11.1/cw20_base
 curl -LO https://github.com/CosmWasm/cw-plus/releases/download/v0.11.1/cw4_group.wasm
 
 # Copy wasm binaries to docker container
-# docker cp artifacts/cw3_dao.wasm cosmwasm:/cw3_dao.wasm
-# docker cp artifacts/cw3_multisig.wasm cosmwasm:/cw3_multisig.wasm
-# docker cp artifacts/stake_cw20.wasm cosmwasm:/stake_cw20.wasm
 docker cp cw20_base.wasm cosmwasm:/cw20_base.wasm
 docker cp cw4_group.wasm cosmwasm:/cw4_group.wasm
 
@@ -65,78 +61,132 @@ sleep 3
 echo "Address to deploy contracts: $1"
 echo "TX Flags: $TXFLAG"
 
-##### UPLOAD CONTRACTS #####
+##### UPLOAD CONTRACT DEPS #####
 
 ### CW20-BASE ###
 CW20_CODE=$(echo xxxxxxxxx | $BINARY tx wasm store "/cw20_base.wasm" --from validator $TXFLAG --output json | jq -r '.logs[0].events[-1].attributes[0].value')
 
-### CW-DAO ###
-CW3_DAO_CODE=$(echo xxxxxxxxx | $BINARY tx wasm store "/cw3_dao.wasm" --from validator $TXFLAG --output json | jq -r '.logs[0].events[-1].attributes[0].value')
-
-### CW3-MULTISIG ###
-CW3_MULTISIG_CODE=$(echo xxxxxxxxx | $BINARY tx wasm store "/cw3_multisig.wasm" --from validator $TXFLAG --output json | jq -r '.logs[0].events[-1].attributes[0].value')
-
 ### CW4-GROUP ###
 CW4_GROUP_CODE=$(echo xxxxxxxxx | $BINARY tx wasm store "/cw4_group.wasm" --from validator $TXFLAG --output json | jq -r '.logs[0].events[-1].attributes[0].value')
 
-### STAKE-CW20 ###
-STAKE_CW20_CODE=$(echo xxxxxxxxx | $BINARY tx wasm store "/stake_cw20.wasm" --from validator $TXFLAG --output json | jq -r '.logs[0].events[-1].attributes[0].value')
+##### UPLOAD DAO DAO CONTRACTS #####
+
+for CONTRACT in ./artifacts/*.wasm; do
+  CONTRACT_NAME=`basename $CONTRACT .wasm`
+  echo "Processing Contract: $CONTRACT_NAME"
+
+  docker cp artifacts/$CONTRACT_NAME.wasm cosmwasm:/$CONTRACT_NAME.wasm
+  CODE_ID=$(echo xxxxxxxxx | $BINARY tx wasm store "/$CONTRACT_NAME.wasm" --from validator $TXFLAG --output json | jq -r '.logs[0].events[-1].attributes[0].value')
 
+  # dynamically create env var to store each contract code id
+  declare ${CONTRACT_NAME}_CODE_ID=$CODE_ID
+done
 
 ##### INSTANTIATE CONTRACTS #####
 
-# Instantiate a DAO contract instantiates its own cw20
-CW3_DAO_INIT='{
-  "name": "DAO DAO",
-  "description": "A DAO that makes DAO tooling",
-  "gov_token": {
-    "instantiate_new_cw20": {
-      "cw20_code_id": '$CW20_CODE',
-      "label": "DAO DAO v0.1.1",
-      "initial_dao_balance": "1000000000",
-      "msg": {
-        "name": "daodao",
-        "symbol": "DAO",
-        "decimals": 6,
-        "initial_balances": [{"address":"'"$1"'","amount":"1000000000"}]
+VOTING_MSG='{
+  "token_info": {
+    "new": {
+      "code_id": '$CW20_CODE',
+      "label": "DAO DAO Gov token",
+      "name": "DAO",
+      "symbol": "DAO",
+      "decimals": 6,
+      "initial_balances": [
+        {
+          "address": "'$1'",
+          "amount": "1000000000000000"
+        }
+      ],
+      "staking_code_id": '$stake_cw20_CODE_ID',
+      "unstaking_duration": {
+        "time": 1209600
       }
     }
-  },
-  "staking_contract": {
-    "instantiate_new_staking_contract": {
-      "staking_contract_code_id": '$STAKE_CW20_CODE'
-    }
-  },
+  }
+}'
+
+echo $VOTING_MSG | jq .
+
+ENCODED_VOTING_MSG=$(echo $VOTING_MSG | base64)
+
+PROPOSAL_MSG='{
   "threshold": {
-    "absolute_percentage": {
-        "percentage": "0.5"
+    "threshold_quorum": {
+      "threshold": {
+        "majority": {}
+      },
+      "quorum": {
+        "percent": "0.1"
+      }
     }
   },
+  "only_members_execute": true,
+  "allow_revoting": false,
   "max_voting_period": {
-    "height": 100
+    "time": 432000
   },
-  "proposal_deposit_amount": "0",
-  "only_members_execute": false,
-  "automatically_add_cw20s": true
+  "deposit_info": {
+    "token": {
+      "voting_module_token": {}
+    },
+    "deposit": "1000000000",
+    "refund_failed_proposals": true
+  }
 }'
-echo $CW3_DAO_INIT | jq .
 
-echo xxxxxxxxx | $BINARY tx wasm instantiate "$CW3_DAO_CODE" "$CW3_DAO_INIT" --from validator --label "DAO DAO" $TXFLAG --output json --no-admin
+echo $PROPOSAL_MSG | jq .
 
-CW3_DAO_CONTRACT=$($BINARY q wasm list-contract-by-code $CW3_DAO_CODE --output json | jq -r '.contracts[-1]')
+ENCODED_PROPOSAL_MSG=$(echo $PROPOSAL_MSG | base64)
+
+# Instantiate a DAO contract instantiates its own cw20
+DAO_INIT='{
+  "name": "DAO DAO",
+  "description": "A DAO that makes DAO tooling",
+  "image_url": "https://zmedley.com/raw_logo.png",
+  "automatically_add_cw20s": false,
+  "automatically_add_cw721s": false,
+  "voting_module_instantiate_info": {
+    "code_id": '$cw20_staked_balance_voting_CODE_ID',
+    "admin": {
+      "core_contract": {}
+    },
+    "label": "DAO DAO Voting Module",
+    "msg": "'$ENCODED_VOTING_MSG'"
+  },
+  "proposal_modules_instantiate_info": [
+    {
+      "code_id": '$cw_proposal_single_CODE_ID',
+      "label": "DAO DAO Proposal Module",
+      "admin": {
+        "core_contract": {}
+      },
+      "msg": "'$ENCODED_PROPOSAL_MSG'"
+    }
+  ]
+}'
+
+echo $DAO_INIT | jq .
+
+echo xxxxxxxxx | $BINARY tx wasm instantiate "$cw_core_CODE_ID" "$DAO_INIT" --from validator --label "DAO DAO" $TXFLAG --output json --no-admin
+
+CW_CORE_DAO_CONTRACT=$($BINARY q wasm list-contract-by-code $cw_core_CODE_ID --output json | jq -r '.contracts[-1]')
 
 # Send some coins to the dao contract to initializae its
 # treasury. Unless this is done the DAO will be unable to perform
 # actions like executing proposals that require it to pay gas fees.
-$BINARY tx bank send validator $CW3_DAO_CONTRACT 9000000$DENOM --chain-id testing $TXFLAG -y
+$BINARY tx bank send validator $CW_CORE_DAO_CONTRACT 9000000$DENOM --chain-id testing $TXFLAG -y
+
 
 # Print out config variables
 printf "\n ------------------------ \n"
 printf "Config Variables \n\n"
 
-echo "NEXT_PUBLIC_DAO_TOKEN_CODE_ID=$CW20_CODE"
-echo "NEXT_PUBLIC_DAO_CONTRACT_CODE_ID=$CW3_DAO_CODE"
-echo "NEXT_PUBLIC_MULTISIG_CODE_ID=$CW3_MULTISIG_CODE"
-echo "NEXT_PUBLIC_C4_GROUP_CODE_ID=$CW4_GROUP_CODE"
-echo "NEXT_PUBLIC_STAKE_CW20_CODE_ID=$STAKE_CW20_CODE"
-echo "NEXT_PUBLIC_DAO_CONTRACT_ADDRESS=$CW3_DAO_CONTRACT"
+echo "NEXT_PUBLIC_CW20_CODE_ID=$CW20_CODE"
+echo "NEXT_PUBLIC_CW4GROUP_CODE_ID=$CW4_GROUP_CODE"
+echo "NEXT_PUBLIC_CWCORE_CODE_ID=$cw_core_CODE_ID"
+echo "NEXT_PUBLIC_CWPROPOSALSINGLE_CODE_ID=$cw_proposal_single_CODE_ID"
+echo "NEXT_PUBLIC_CW4VOTING_CODE_ID=$cw4_voting_CODE_ID"
+echo "NEXT_PUBLIC_CW20STAKEDBALANCEVOTING_CODE_ID=$cw20_staked_balance_voting_CODE_ID"
+echo "NEXT_PUBLIC_STAKECW20_CODE_ID=$stake_cw20_CODE_ID"
+echo "NEXT_PUBLIC_DAO_CONTRACT_ADDRESS=$CW_CORE_DAO_CONTRACT"
diff --git a/scripts/deploy_local_legacy.sh b/scripts/deploy_local_legacy.sh
new file mode 100755
index 00000000..9234e6db
--- /dev/null
+++ b/scripts/deploy_local_legacy.sh
@@ -0,0 +1,139 @@
+#!/bin/bash
+
+# Run this from the root repo directory
+
+## CONFIG
+# NOTE: you will need to update these to deploy on different network
+IMAGE_TAG=${2:-"v2.3.0-beta.1"} # lupercalia beta - this allows you to pass in an image, e.g. pr-156 as arg 2
+CONTAINER_NAME="cosmwasm"
+BINARY="docker exec -i $CONTAINER_NAME junod"
+DENOM='ujunox'
+CHAIN_ID='testing'
+RPC='http://localhost:26657/'
+TXFLAG="--gas-prices 0.1$DENOM --gas auto --gas-adjustment 1.5 -y -b block --chain-id $CHAIN_ID --node $RPC"
+BLOCK_GAS_LIMIT=${GAS_LIMIT:-100000000} # should mirror mainnet
+
+echo "Building $IMAGE_TAG"
+echo "Configured Block Gas Limit: $BLOCK_GAS_LIMIT"
+
+if [ "$1" = "" ]
+then
+  echo "Usage: $0 1 arg required, wasm address. See \"Deploying in a development environment\" in README."
+  exit
+fi
+
+# kill any orphans
+docker kill $CONTAINER_NAME
+docker volume rm -f junod_data
+
+# Run junod setup script
+docker run --rm -d --name $CONTAINER_NAME \
+    -e PASSWORD=xxxxxxxxx \
+    -e STAKE_TOKEN=$DENOM \
+    -e GAS_LIMIT="$GAS_LIMIT" \
+    -e UNSAFE_CORS=true \
+    -p 1317:1317 -p 26656:26656 -p 26657:26657 \
+    --mount type=volume,source=junod_data,target=/root \
+    ghcr.io/cosmoscontracts/juno:$IMAGE_TAG /opt/setup_and_run.sh $1
+
+# Compile code
+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/amd64 \
+  cosmwasm/workspace-optimizer:0.12.6
+
+# Download cw20_base.wasm
+curl -LO https://github.com/CosmWasm/cw-plus/releases/download/v0.11.1/cw20_base.wasm
+# Download c4_group.wasm
+curl -LO https://github.com/CosmWasm/cw-plus/releases/download/v0.11.1/cw4_group.wasm
+
+# Copy wasm binaries to docker container
+docker cp cw20_base.wasm cosmwasm:/cw20_base.wasm
+docker cp cw4_group.wasm cosmwasm:/cw4_group.wasm
+
+## Clean up
+rm cw20_base.wasm
+rm cw4_group.wasm
+
+# Sleep while waiting for chain to post genesis block
+sleep 3
+
+echo "Address to deploy contracts: $1"
+echo "TX Flags: $TXFLAG"
+
+##### UPLOAD CONTRACTS #####
+
+### CW20-BASE ###
+CW20_CODE=$(echo xxxxxxxxx | $BINARY tx wasm store "/cw20_base.wasm" --from validator $TXFLAG --output json | jq -r '.logs[0].events[-1].attributes[0].value')
+
+### CW-DAO ###
+CW3_DAO_CODE=$(echo xxxxxxxxx | $BINARY tx wasm store "/cw3_dao.wasm" --from validator $TXFLAG --output json | jq -r '.logs[0].events[-1].attributes[0].value')
+
+### CW3-MULTISIG ###
+CW3_MULTISIG_CODE=$(echo xxxxxxxxx | $BINARY tx wasm store "/cw3_multisig.wasm" --from validator $TXFLAG --output json | jq -r '.logs[0].events[-1].attributes[0].value')
+
+### CW4-GROUP ###
+CW4_GROUP_CODE=$(echo xxxxxxxxx | $BINARY tx wasm store "/cw4_group.wasm" --from validator $TXFLAG --output json | jq -r '.logs[0].events[-1].attributes[0].value')
+
+### STAKE-CW20 ###
+STAKE_CW20_CODE=$(echo xxxxxxxxx | $BINARY tx wasm store "/stake_cw20.wasm" --from validator $TXFLAG --output json | jq -r '.logs[0].events[-1].attributes[0].value')
+
+
+##### INSTANTIATE CONTRACTS #####
+
+# Instantiate a DAO contract instantiates its own cw20
+CW3_DAO_INIT='{
+  "name": "DAO DAO",
+  "description": "A DAO that makes DAO tooling",
+  "gov_token": {
+    "instantiate_new_cw20": {
+      "cw20_code_id": '$CW20_CODE',
+      "label": "DAO DAO v0.1.1",
+      "initial_dao_balance": "1000000000",
+      "msg": {
+        "name": "daodao",
+        "symbol": "DAO",
+        "decimals": 6,
+        "initial_balances": [{"address":"'"$1"'","amount":"1000000000"}]
+      }
+    }
+  },
+  "staking_contract": {
+    "instantiate_new_staking_contract": {
+      "staking_contract_code_id": '$STAKE_CW20_CODE'
+    }
+  },
+  "threshold": {
+    "absolute_percentage": {
+        "percentage": "0.5"
+    }
+  },
+  "max_voting_period": {
+    "height": 100
+  },
+  "proposal_deposit_amount": "0",
+  "only_members_execute": false,
+  "automatically_add_cw20s": true
+}'
+echo $CW3_DAO_INIT | jq .
+
+echo xxxxxxxxx | $BINARY tx wasm instantiate "$CW3_DAO_CODE" "$CW3_DAO_INIT" --from validator --label "DAO DAO" $TXFLAG --output json --no-admin
+
+CW3_DAO_CONTRACT=$($BINARY q wasm list-contract-by-code $CW3_DAO_CODE --output json | jq -r '.contracts[-1]')
+
+# Send some coins to the dao contract to initializae its
+# treasury. Unless this is done the DAO will be unable to perform
+# actions like executing proposals that require it to pay gas fees.
+$BINARY tx bank send validator $CW3_DAO_CONTRACT 9000000$DENOM --chain-id testing $TXFLAG -y
+
+# Print out config variables
+printf "\n ------------------------ \n"
+printf "Config Variables \n\n"
+
+echo "NEXT_PUBLIC_DAO_TOKEN_CODE_ID=$CW20_CODE"
+echo "NEXT_PUBLIC_DAO_CONTRACT_CODE_ID=$CW3_DAO_CODE"
+echo "NEXT_PUBLIC_MULTISIG_CODE_ID=$CW3_MULTISIG_CODE"
+echo "NEXT_PUBLIC_C4_GROUP_CODE_ID=$CW4_GROUP_CODE"
+echo "NEXT_PUBLIC_STAKE_CW20_CODE_ID=$STAKE_CW20_CODE"
+echo "NEXT_PUBLIC_DAO_CONTRACT_ADDRESS=$CW3_DAO_CONTRACT"


Test patch:
diff --git a/contracts/cw-core/src/tests.rs b/contracts/cw-core/src/tests.rs
index 79e00fdd..68cf1578 100644
--- a/contracts/cw-core/src/tests.rs
+++ b/contracts/cw-core/src/tests.rs
@@ -1073,11 +1073,16 @@ fn test_add_remove_get() {
         }
     );
 
-    remove_item(&mut app, gov_addr.clone(), "a".to_string());
-    let a = get_item(&mut app, gov_addr.clone(), "a".to_string());
+    remove_item(&mut app, gov_addr.clone(), "aaaaakey".to_string());
+    let a = get_item(&mut app, gov_addr, "aaaaakey".to_string());
     assert_eq!(a, GetItemResponse { item: None });
+}
 
-    remove_item(&mut app, gov_addr, "b".to_string());
+#[test]
+#[should_panic(expected = "Key is missing from storage")]
+fn test_remove_missing_key() {
+    let (gov_addr, mut app) = do_standard_instantiate(true, None);
+    remove_item(&mut app, gov_addr, "b".to_string())
 }
 
 #[test]
diff --git a/contracts/cw4-voting/src/tests.rs b/contracts/cw4-voting/src/tests.rs
index 6b07dfb0..4e8927ba 100644
--- a/contracts/cw4-voting/src/tests.rs
+++ b/contracts/cw4-voting/src/tests.rs
@@ -14,6 +14,7 @@ const DAO_ADDR: &str = "dao";
 const ADDR1: &str = "addr1";
 const ADDR2: &str = "addr2";
 const ADDR3: &str = "addr3";
+const ADDR4: &str = "addr4";
 
 fn cw4_contract() -> Box<dyn Contract<Empty>> {
     let contract = ContractWrapper::new(
@@ -64,6 +65,10 @@ fn setup_test_case(app: &mut App) -> Addr {
             addr: ADDR3.to_string(),
             weight: 1,
         },
+        cw4::Member {
+            addr: ADDR4.to_string(),
+            weight: 0,
+        },
     ];
     instantiate_voting(
         app,
@@ -585,3 +590,77 @@ fn test_duplicate_member() {
         )
         .unwrap_err();
 }
+
+#[test]
+fn test_zero_voting_power() {
+    let mut app = App::default();
+    let voting_addr = setup_test_case(&mut app);
+    app.update_block(next_block);
+
+    let cw4_addr: Addr = app
+        .wrap()
+        .query_wasm_smart(voting_addr.clone(), &QueryMsg::GroupContract {})
+        .unwrap();
+
+    // check that ADDR4 weight is 0
+    let addr4_voting_power: VotingPowerAtHeightResponse = app
+        .wrap()
+        .query_wasm_smart(
+            voting_addr.clone(),
+            &QueryMsg::VotingPowerAtHeight {
+                address: ADDR4.to_string(),
+                height: None,
+            },
+        )
+        .unwrap();
+    assert_eq!(addr4_voting_power.power, Uint128::new(0));
+    assert_eq!(addr4_voting_power.height, app.block_info().height);
+
+    // Update ADDR1's weight to 0
+    let msg = cw4_group::msg::ExecuteMsg::UpdateMembers {
+        remove: vec![],
+        add: vec![cw4::Member {
+            addr: ADDR1.to_string(),
+            weight: 0,
+        }],
+    };
+    app.execute_contract(Addr::unchecked(DAO_ADDR), cw4_addr, &msg, &[])
+        .unwrap();
+
+    // Should still be one as voting power should not update until
+    // the following block.
+    let addr1_voting_power: VotingPowerAtHeightResponse = app
+        .wrap()
+        .query_wasm_smart(
+            voting_addr.clone(),
+            &QueryMsg::VotingPowerAtHeight {
+                address: ADDR1.to_string(),
+                height: None,
+            },
+        )
+        .unwrap();
+    assert_eq!(addr1_voting_power.power, Uint128::new(1u128));
+
+    // update block to see the changes
+    app.update_block(next_block);
+    let addr1_voting_power: VotingPowerAtHeightResponse = app
+        .wrap()
+        .query_wasm_smart(
+            voting_addr.clone(),
+            &QueryMsg::VotingPowerAtHeight {
+                address: ADDR1.to_string(),
+                height: None,
+            },
+        )
+        .unwrap();
+    assert_eq!(addr1_voting_power.power, Uint128::new(0u128));
+    assert_eq!(addr1_voting_power.height, app.block_info().height);
+
+    // Check total power is now 2
+    let total_voting_power: TotalPowerAtHeightResponse = app
+        .wrap()
+        .query_wasm_smart(voting_addr, &QueryMsg::TotalPowerAtHeight { height: None })
+        .unwrap();
+    assert_eq!(total_voting_power.power, Uint128::new(2u128));
+    assert_eq!(total_voting_power.height, app.block_info().height);
+}