Repository: DA0-DA0/dao-contracts
Pull request #256 (2022-04-22 03:05:33)

Run voting tests against all three voting modules.
Also updates dependencies so that we have the latest and greatest
versions.

JakeHartnell: Very cool.
ezekiiel: Rebased on main.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 32b5b981..e569e30d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8,12 +8,6 @@ version = "1.0.57"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
 
-[[package]]
-name = "assert_matches"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
-
 [[package]]
 name = "base64"
 version = "0.13.0"
@@ -87,13 +81,14 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-std"
-version = "1.0.0-beta5"
+version = "1.0.0-beta8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2ece12e5bbde434b93937d7b2107e6291f11d69ffa72398c50e8bab41d451d3"
+checksum = "915ca82bd944f116f3a9717481f3fa657e4a73f28c4887288761ebb24e6fbe10"
 dependencies = [
  "base64",
  "cosmwasm-crypto",
  "cosmwasm-derive",
+ "forward_ref",
  "schemars",
  "serde",
  "serde-json-wasm",
@@ -103,9 +98,9 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-storage"
-version = "1.0.0-beta5"
+version = "1.0.0-beta8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7e4338d8e9934effa4594f139ce4e5f4b426796b70e2d53bb7e8605e9adf68c"
+checksum = "1c4be9fd8c9d3ae7d0c32a925ecbc20707007ce0cba1f7538c0d78b7a2d3729b"
 dependencies = [
  "cosmwasm-std",
  "serde",
@@ -163,13 +158,13 @@ dependencies = [
 
 [[package]]
 name = "cw-controllers"
-version = "0.11.1"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64bfeaf55f8dba5646cc3daddce17cd23a60f8e0c3fbacbe6735d287d7a6e33a"
+checksum = "1bc6d042b14823b0e9f33f5cdd67a1eb9b16a7d79f7547b1a73c8870b518b97b"
 dependencies = [
  "cosmwasm-std",
- "cw-storage-plus 0.11.1",
- "cw-utils 0.11.1",
+ "cw-storage-plus",
+ "cw-utils",
  "schemars",
  "serde",
  "thiserror",
@@ -184,14 +179,14 @@ dependencies = [
  "cosmwasm-storage",
  "cw-core-interface",
  "cw-core-macros",
- "cw-multi-test 0.12.1",
+ "cw-multi-test",
  "cw-proposal-sudo",
- "cw-storage-plus 0.12.1",
- "cw-utils 0.12.1",
- "cw2 0.12.1",
- "cw20 0.12.1",
+ "cw-storage-plus",
+ "cw-utils",
+ "cw2",
+ "cw20",
  "cw20-balance-voting",
- "cw20-base 0.12.1",
+ "cw20-base",
  "cw721",
  "cw721-base",
  "schemars",
@@ -206,7 +201,7 @@ dependencies = [
  "cosmwasm-schema",
  "cosmwasm-std",
  "cw-core-macros",
- "cw2 0.12.1",
+ "cw2",
  "schemars",
  "serde",
 ]
@@ -225,34 +220,15 @@ dependencies = [
 
 [[package]]
 name = "cw-multi-test"
-version = "0.11.1"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14b44e3ccc6ead7e8c0ecc682e8ab634ce1dec8a2ac97b7b44e84477695adc55"
+checksum = "cbea57e5be4a682268a5eca1a57efece57a54ff216bfd87603d5e864aad40e12"
 dependencies = [
  "anyhow",
  "cosmwasm-std",
  "cosmwasm-storage",
- "cw-storage-plus 0.11.1",
- "cw-utils 0.11.1",
- "derivative",
- "itertools",
- "prost",
- "schemars",
- "serde",
- "thiserror",
-]
-
-[[package]]
-name = "cw-multi-test"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5363a1903f8e5dd480240fdf007177fc11b3511097a06c5601a885cf619cdd5"
-dependencies = [
- "anyhow",
- "cosmwasm-std",
- "cosmwasm-storage",
- "cw-storage-plus 0.12.1",
- "cw-utils 0.12.1",
+ "cw-storage-plus",
+ "cw-utils",
  "derivative",
  "itertools",
  "prost",
@@ -268,9 +244,9 @@ dependencies = [
  "cosmwasm-schema",
  "cosmwasm-std",
  "cosmwasm-storage",
- "cw-multi-test 0.12.1",
- "cw-storage-plus 0.12.1",
- "cw2 0.12.1",
+ "cw-multi-test",
+ "cw-storage-plus",
+ "cw2",
  "schemars",
  "serde",
  "thiserror",
@@ -286,21 +262,24 @@ dependencies = [
  "cw-core",
  "cw-core-interface",
  "cw-core-macros",
- "cw-multi-test 0.12.1",
- "cw-storage-plus 0.12.1",
- "cw-utils 0.12.1",
- "cw2 0.12.1",
- "cw20 0.12.1",
+ "cw-multi-test",
+ "cw-storage-plus",
+ "cw-utils",
+ "cw2",
+ "cw20",
  "cw20-balance-voting",
- "cw20-base 0.12.1",
+ "cw20-base",
  "cw20-staked-balance-voting",
- "cw3 0.12.1",
+ "cw3",
+ "cw4",
+ "cw4-group",
+ "cw4-voting",
  "indexable-hooks",
  "proposal-hooks",
- "rand",
  "schemars",
  "serde",
  "stake-cw20",
+ "testing",
  "thiserror",
  "vote-hooks",
  "voting",
@@ -316,9 +295,9 @@ dependencies = [
  "cw-core",
  "cw-core-interface",
  "cw-core-macros",
- "cw-multi-test 0.12.1",
- "cw-storage-plus 0.12.1",
- "cw2 0.12.1",
+ "cw-multi-test",
+ "cw-storage-plus",
+ "cw2",
  "schemars",
  "serde",
  "thiserror",
@@ -326,20 +305,9 @@ dependencies = [
 
 [[package]]
 name = "cw-storage-plus"
-version = "0.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d7ee1963302b0ac2a9d42fe0faec826209c17452bfd36fbfd9d002a88929261"
-dependencies = [
- "cosmwasm-std",
- "schemars",
- "serde",
-]
-
-[[package]]
-name = "cw-storage-plus"
-version = "0.12.1"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c087ff98fb0475db4c2b5298a5fd12b2848d2854b39d1115d930ee6da24d1eed"
+checksum = "9336ecef1e19d56cf6e3e932475fc6a3dee35eec5a386e07917a1d1ba6bb0e35"
 dependencies = [
  "cosmwasm-std",
  "schemars",
@@ -348,9 +316,9 @@ dependencies = [
 
 [[package]]
 name = "cw-utils"
-version = "0.11.1"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef842a1792e4285beff7b3b518705f760fa4111dc1e296e53f3e92d1ef7f6220"
+checksum = "babd2c090f39d07ce5bf2556962305e795daa048ce20a93709eb591476e4a29e"
 dependencies = [
  "cosmwasm-std",
  "schemars",
@@ -358,62 +326,26 @@ dependencies = [
  "thiserror",
 ]
 
-[[package]]
-name = "cw-utils"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3396c7aff5a0e3fb6dcc6cc89f56862c1d212b40d93ed725a6962955b1887ff"
-dependencies = [
- "cosmwasm-std",
- "schemars",
- "serde",
- "thiserror",
-]
-
-[[package]]
-name = "cw2"
-version = "0.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1d81d7c359d6c1fba3aa83dad7ec6f999e512571380ae62f81257c3db569743"
-dependencies = [
- "cosmwasm-std",
- "cw-storage-plus 0.11.1",
- "schemars",
- "serde",
-]
-
 [[package]]
 name = "cw2"
-version = "0.12.1"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f8a6500c396e33f6a7b05d35a5124eb3e394cdb6ca901f7e88332870407896c"
+checksum = "993df11574f29574dd443eb0c189484bb91bc0638b6de3e32ab7f9319c92122d"
 dependencies = [
  "cosmwasm-std",
- "cw-storage-plus 0.12.1",
+ "cw-storage-plus",
  "schemars",
  "serde",
 ]
 
 [[package]]
 name = "cw20"
-version = "0.11.1"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9671d7edef5608acaf5b2f1e473ee3f501eced2cd4f7392e2106c8cf02ba0720"
+checksum = "356d364602c5fe763544ea00d485b825d6ef519a2fc6a3145528d7df3a603f40"
 dependencies = [
  "cosmwasm-std",
- "cw-utils 0.11.1",
- "schemars",
- "serde",
-]
-
-[[package]]
-name = "cw20"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "413911037f6b0106ae37ca82352f9131939ccc995aade435df47a5baf2c815f2"
-dependencies = [
- "cosmwasm-std",
- "cw-utils 0.12.1",
+ "cw-utils",
  "schemars",
  "serde",
 ]
@@ -427,12 +359,12 @@ dependencies = [
  "cosmwasm-storage",
  "cw-core-interface",
  "cw-core-macros",
- "cw-multi-test 0.12.1",
- "cw-storage-plus 0.12.1",
- "cw-utils 0.12.1",
- "cw2 0.12.1",
- "cw20 0.12.1",
- "cw20-base 0.12.1",
+ "cw-multi-test",
+ "cw-storage-plus",
+ "cw-utils",
+ "cw2",
+ "cw20",
+ "cw20-base",
  "schemars",
  "serde",
  "thiserror",
@@ -440,31 +372,15 @@ dependencies = [
 
 [[package]]
 name = "cw20-base"
-version = "0.11.1"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62f6fc8c4cd451b418fa4f1ac2ea70595811fa9d8b4033617fe47953d7a93ceb"
+checksum = "3b370e5fb6b35db58cabd7903b8f9bd4c3bcff701c63bf1a7185a84e60bfa502"
 dependencies = [
  "cosmwasm-std",
- "cw-storage-plus 0.11.1",
- "cw-utils 0.11.1",
- "cw2 0.11.1",
- "cw20 0.11.1",
- "schemars",
- "serde",
- "thiserror",
-]
-
-[[package]]
-name = "cw20-base"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef3f8c150d7c5cf14e17ee7ee59b8e7bb4420506ccbbc9bd127236faea747afd"
-dependencies = [
- "cosmwasm-std",
- "cw-storage-plus 0.12.1",
- "cw-utils 0.12.1",
- "cw2 0.12.1",
- "cw20 0.12.1",
+ "cw-storage-plus",
+ "cw-utils",
+ "cw2",
+ "cw20",
  "schemars",
  "serde",
  "thiserror",
@@ -479,12 +395,12 @@ dependencies = [
  "cosmwasm-storage",
  "cw-core-interface",
  "cw-core-macros",
- "cw-multi-test 0.12.1",
- "cw-storage-plus 0.12.1",
- "cw-utils 0.11.1",
- "cw2 0.12.1",
- "cw20 0.12.1",
- "cw20-base 0.12.1",
+ "cw-multi-test",
+ "cw-storage-plus",
+ "cw-utils",
+ "cw2",
+ "cw20",
+ "cw20-base",
  "schemars",
  "serde",
  "stake-cw20",
@@ -493,111 +409,40 @@ dependencies = [
 
 [[package]]
 name = "cw3"
-version = "0.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c82e63276f517fe9bafc1ddca19d091367d715d54199708c4ea3c8c58c51936"
-dependencies = [
- "cosmwasm-std",
- "cw-utils 0.11.1",
- "schemars",
- "serde",
-]
-
-[[package]]
-name = "cw3"
-version = "0.12.1"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "017b6263414c58081cea97626f4d8da2dc8f7267337bba0eb5770260d26251e0"
-dependencies = [
- "cosmwasm-std",
- "cw-utils 0.12.1",
- "schemars",
- "serde",
-]
-
-[[package]]
-name = "cw3-dao"
-version = "0.2.6"
-dependencies = [
- "cosmwasm-schema",
- "cosmwasm-std",
- "cw-multi-test 0.11.1",
- "cw-storage-plus 0.11.1",
- "cw-utils 0.11.1",
- "cw2 0.11.1",
- "cw20 0.11.1",
- "cw20-base 0.11.1",
- "cw3 0.11.1",
- "schemars",
- "serde",
- "stake-cw20",
- "thiserror",
-]
-
-[[package]]
-name = "cw3-multisig"
-version = "0.2.5"
+checksum = "f871854338a54c7bb094d16ffe17212b93b146d9659dbce4c9402a9b77e240ef"
 dependencies = [
- "cosmwasm-schema",
  "cosmwasm-std",
- "cw-multi-test 0.11.1",
- "cw-storage-plus 0.11.1",
- "cw-utils 0.11.1",
- "cw2 0.11.1",
- "cw20 0.11.1",
- "cw20-base 0.11.1",
- "cw3 0.11.1",
- "cw4",
- "cw4-group",
+ "cw-utils",
  "schemars",
  "serde",
- "thiserror",
 ]
 
 [[package]]
 name = "cw4"
-version = "0.11.1"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41445666e3f6284c72f569b2240bb52d0add94bb448e169d30ae28fe000cfaeb"
+checksum = "c4476d6a7c13c46ed9ff260bd0e1cf648dc37b13f483822e1ff2a431f0f6ee52"
 dependencies = [
  "cosmwasm-std",
- "cw-storage-plus 0.11.1",
+ "cw-storage-plus",
  "schemars",
  "serde",
 ]
 
 [[package]]
 name = "cw4-group"
-version = "0.11.1"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db3389be9a6d350cc5ed9918af4070007c9acc360e515788a10885536b972764"
+checksum = "5503e4627160fc3c008cdb433df9392ea204c51259c0c58290c1e795db88c88e"
 dependencies = [
  "cosmwasm-std",
  "cw-controllers",
- "cw-storage-plus 0.11.1",
- "cw-utils 0.11.1",
- "cw2 0.11.1",
- "cw4",
- "schemars",
- "serde",
- "thiserror",
-]
-
-[[package]]
-name = "cw4-registry"
-version = "0.2.5"
-dependencies = [
- "anyhow",
- "assert_matches",
- "cosmwasm-schema",
- "cosmwasm-std",
- "cosmwasm-storage",
- "cw-multi-test 0.11.1",
- "cw-storage-plus 0.11.1",
- "cw-utils 0.11.1",
- "cw2 0.11.1",
+ "cw-storage-plus",
+ "cw-utils",
+ "cw2",
  "cw4",
- "cw4-group",
  "schemars",
  "serde",
  "thiserror",
@@ -612,10 +457,10 @@ dependencies = [
  "cosmwasm-storage",
  "cw-core-interface",
  "cw-core-macros",
- "cw-multi-test 0.12.1",
- "cw-storage-plus 0.12.1",
- "cw-utils 0.12.1",
- "cw2 0.12.1",
+ "cw-multi-test",
+ "cw-storage-plus",
+ "cw-utils",
+ "cw2",
  "cw4",
  "cw4-group",
  "schemars",
@@ -625,26 +470,26 @@ dependencies = [
 
 [[package]]
 name = "cw721"
-version = "0.12.0"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc5bb00baf493fc48bf05831edc3ed074731da7c1944c8521ead3df4ff08d9c7"
+checksum = "72b9fd71276795554c35899bb3a378561ed0c288d231113e9915f6ee1f42b7b5"
 dependencies = [
  "cosmwasm-std",
- "cw-utils 0.12.1",
+ "cw-utils",
  "schemars",
  "serde",
 ]
 
 [[package]]
 name = "cw721-base"
-version = "0.12.0"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da773ac93cae5a01a052772d65a2f377dd22933f583ef8f739d67f32ffce1b6b"
+checksum = "b61200af4e027af2d7485dbdc37c2a9c4093b6b2f2b811732329ef456076f97e"
 dependencies = [
  "cosmwasm-std",
- "cw-storage-plus 0.12.1",
- "cw-utils 0.12.1",
- "cw2 0.12.1",
+ "cw-storage-plus",
+ "cw-utils",
+ "cw2",
  "cw721",
  "schemars",
  "serde",
@@ -745,6 +590,12 @@ dependencies = [
  "subtle",
 ]
 
+[[package]]
+name = "forward_ref"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e"
+
 [[package]]
 name = "generic-array"
 version = "0.14.5"
@@ -809,7 +660,7 @@ name = "indexable-hooks"
 version = "0.1.0"
 dependencies = [
  "cosmwasm-std",
- "cw-storage-plus 0.12.1",
+ "cw-storage-plus",
  "schemars",
  "serde",
  "thiserror",
@@ -897,14 +748,14 @@ dependencies = [
  "cosmwasm-std",
  "cosmwasm-storage",
  "cw-core",
- "cw-multi-test 0.12.1",
+ "cw-multi-test",
  "cw-proposal-single",
- "cw-storage-plus 0.12.1",
- "cw-utils 0.12.1",
- "cw2 0.12.1",
- "cw20 0.12.1",
+ "cw-storage-plus",
+ "cw-utils",
+ "cw2",
+ "cw20",
  "cw20-balance-voting",
- "cw20-base 0.12.1",
+ "cw20-base",
  "indexable-hooks",
  "proposal-hooks",
  "schemars",
@@ -1107,12 +958,12 @@ dependencies = [
  "cosmwasm-std",
  "cosmwasm-storage",
  "cw-controllers",
- "cw-multi-test 0.11.1",
- "cw-storage-plus 0.11.1",
- "cw-utils 0.11.1",
- "cw2 0.11.1",
- "cw20 0.11.1",
- "cw20-base 0.11.1",
+ "cw-multi-test",
+ "cw-storage-plus",
+ "cw-utils",
+ "cw2",
+ "cw20",
+ "cw20-base",
  "schemars",
  "serde",
  "thiserror",
@@ -1127,12 +978,12 @@ dependencies = [
  "cosmwasm-std",
  "cosmwasm-storage",
  "cw-controllers",
- "cw-multi-test 0.11.1",
- "cw-storage-plus 0.11.1",
- "cw-utils 0.11.1",
- "cw2 0.11.1",
- "cw20 0.11.1",
- "cw20-base 0.11.1",
+ "cw-multi-test",
+ "cw-storage-plus",
+ "cw-utils",
+ "cw2",
+ "cw20",
+ "cw20-base",
  "schemars",
  "serde",
  "stake-cw20",
@@ -1162,6 +1013,16 @@ dependencies = [
  "unicode-xid",
 ]
 
+[[package]]
+name = "testing"
+version = "0.1.0"
+dependencies = [
+ "cosmwasm-std",
+ "cw-multi-test",
+ "rand",
+ "voting",
+]
+
 [[package]]
 name = "thiserror"
 version = "1.0.30"
@@ -1190,9 +1051,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 
 [[package]]
 name = "uint"
-version = "0.9.1"
+version = "0.9.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f"
+checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0"
 dependencies = [
  "byteorder",
  "crunchy",
@@ -1229,6 +1090,7 @@ dependencies = [
  "cosmwasm-std",
  "schemars",
  "serde",
+ "thiserror",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 00d8ff0d..23aa934a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,17 +1,5 @@
 [workspace]
-members = ["contracts/*", "packages/*", "legacy/*"] 
-
-[profile.release.package.cw3-dao]
-codegen-units = 1
-incremental = false
-
-[profile.release.package.cw3-multisig]
-codegen-units = 1
-incremental = false
-
-[profile.release.package.stake-cw20]
-codegen-units = 1
-incremental = false
+members = ["contracts/*", "packages/*"]  
 
 
 [profile.release.package.stake-cw20-external-rewards]
@@ -19,6 +7,7 @@ codegen-units = 1
 incremental = false
 
 [profile.release]
+codegen-units = 1
 opt-level = 3
 debug = false
 rpath = false
@@ -27,3 +16,4 @@ debug-assertions = false
 panic = 'abort'
 incremental = false
 overflow-checks = true
+ 
\ No newline at end of file
diff --git a/contracts/cw-core/Cargo.toml b/contracts/cw-core/Cargo.toml
index b726b527..2070026b 100644
--- a/contracts/cw-core/Cargo.toml
+++ b/contracts/cw-core/Cargo.toml
@@ -29,23 +29,23 @@ optimize = """docker run --rm -v "$(pwd)":/code \
 """
 
 [dependencies]
-cosmwasm-std = { version = "1.0.0-beta5" }
-cosmwasm-storage = { version = "1.0.0-beta5" }
-cw-storage-plus = "0.12"
-cw2 = "0.12"
-cw-utils = "0.12"
-cw20 = "0.12"
-cw721 = "0.12"
+cosmwasm-std = { version = "1.0.0-beta" }
+cosmwasm-storage = { version = "1.0.0-beta" }
+cw-storage-plus = "0.13"
+cw2 = "0.13"
+cw-utils = "0.13"
+cw20 = "0.13"
+cw721 = "0.13"
 schemars = "0.8"
 serde = { version = "1.0", default-features = false, features = ["derive"] }
 thiserror = { version = "1.0" }
-cw-core-interface = { version = "0.1.0", path = "../../packages/cw-core-interface" } 
+cw-core-interface = { version = "0.1.0", path = "../../packages/cw-core-interface" }
 cw-core-macros = { version = "0.1.0", path = "../../packages/cw-core-macros" }
 
 [dev-dependencies]
-cosmwasm-schema = { version = "1.0.0-beta5" }
-cw-multi-test = "0.12"
-cw20-base = "0.12"
-cw721-base = "0.12"
+cosmwasm-schema = { version = "1.0.0-beta" }
+cw-multi-test = "0.13"
+cw20-base = "0.13"
+cw721-base = "0.13"
 cw-proposal-sudo = { version = "0.1.0", path = "../cw-proposal-sudo"}
 cw20-balance-voting = { version = "0.1.0", path = "../cw20-balance-voting"}
diff --git a/contracts/cw-named-groups/Cargo.toml b/contracts/cw-named-groups/Cargo.toml
index 88f917eb..1f694b81 100644
--- a/contracts/cw-named-groups/Cargo.toml
+++ b/contracts/cw-named-groups/Cargo.toml
@@ -29,14 +29,14 @@ optimize = """docker run --rm -v "$(pwd)":/code \
 """
 
 [dependencies]
-cosmwasm-std = { version = "= 1.0.0-beta5" }
-cosmwasm-storage = { version = "1.0.0-beta5" }
-cw-storage-plus = "0.12"
-cw2 = "0.12"
+cosmwasm-std = { version = "1.0.0-beta" }
+cosmwasm-storage = { version = "1.0.0-beta" }
+cw-storage-plus = "0.13"
+cw2 = "0.13"
 schemars = "0.8"
 serde = { version = "1.0", default-features = false, features = ["derive"] }
 thiserror = { version = "1.0" }
 
 [dev-dependencies]
-cosmwasm-schema = { version = "1.0.0-beta5" }
-cw-multi-test = "0.12"
+cosmwasm-schema = { version = "1.0.0-beta" }
+cw-multi-test = "0.13"
diff --git a/contracts/cw-proposal-single/Cargo.toml b/contracts/cw-proposal-single/Cargo.toml
index a13b3e42..0c6b1adf 100644
--- a/contracts/cw-proposal-single/Cargo.toml
+++ b/contracts/cw-proposal-single/Cargo.toml
@@ -29,29 +29,33 @@ optimize = """docker run --rm -v "$(pwd)":/code \
 """
 
 [dependencies]
-cosmwasm-std = { version = "1.0.0-beta5" }
-cosmwasm-storage = { version = "1.0.0-beta5" }
-cw-storage-plus = "0.12"
-cw-utils = "0.12"
-cw2 = "0.12"
-cw20 = "0.12"
-cw3 = "0.12"
+cosmwasm-std = { version = "1.0.0-beta" }
+cosmwasm-storage = { version = "1.0.0-beta" }
+cw-storage-plus = "0.13"
+cw-utils = "0.13"
+cw2 = "0.13"
+cw20 = "0.13"
+cw3 = "0.13"
 schemars = "0.8"
 serde = { version = "1.0", default-features = false, features = ["derive"] }
 thiserror = { version = "1.0" }
-cw-core = { path = "../cw-core", version = "0.1.0", features = ["library"] }
-cw-core-macros = { path = "../../packages/cw-core-macros", version = "0.1.0" }
-cw-core-interface = { version = "0.1.0", path = "../../packages/cw-core-interface" } 
-voting = { version = "0.1.0", path = "../../packages/voting" }
-indexable-hooks = { version = "0.1.0", path = "../../packages/indexable-hooks" }
-proposal-hooks = { version = "0.1.0", path = "../../packages/proposal-hooks" }
-vote-hooks = { version = "0.1.0", path = "../../packages/vote-hooks" }
+cw-core = { path = "../cw-core", version = "*", features = ["library"] }
+cw-core-macros = { path = "../../packages/cw-core-macros", version = "*" }
+cw-core-interface = { version = "*", path = "../../packages/cw-core-interface" }
+voting = { version = "*", path = "../../packages/voting" }
+indexable-hooks = { version = "*", path = "../../packages/indexable-hooks" }
+proposal-hooks = { version = "*", path = "../../packages/proposal-hooks" }
+vote-hooks = { version = "*", path = "../../packages/vote-hooks" }
 
 [dev-dependencies]
-cosmwasm-schema = { version = "1.0.0-beta5" }
-cw-multi-test = "0.12"
-cw20-balance-voting = { path = "../cw20-balance-voting", version = "0.1.0" }
-cw20-staked-balance-voting = { path = "../cw20-staked-balance-voting", version = "0.1.0" }
-stake-cw20 = { path = "../stake-cw20" }
-cw20-base = "0.12"
-rand = "0.8"
\ No newline at end of file
+cosmwasm-schema = { version = "1.0.0-beta" }
+cw-multi-test = "0.13"
+cw4-voting = { path = "../cw4-voting", version = "*" }
+cw20-balance-voting = { path = "../cw20-balance-voting", version = "*" }
+cw20-staked-balance-voting = { path = "../cw20-staked-balance-voting", version = "*" }
+testing = { version = "*", path = "../../packages/testing" }
+stake-cw20 = { path="../stake-cw20", version = "*" }
+cw20-base = "0.13"
+cw4 = "0.13"
+cw4-group = "0.13"
+
diff --git a/contracts/cw-proposal-single/src/contract.rs b/contracts/cw-proposal-single/src/contract.rs
index 06f79662..8f069f0a 100644
--- a/contracts/cw-proposal-single/src/contract.rs
+++ b/contracts/cw-proposal-single/src/contract.rs
@@ -12,19 +12,18 @@ use indexable_hooks::Hooks;
 use proposal_hooks::{new_proposal_hooks, proposal_status_changed_hooks};
 use vote_hooks::new_vote_hooks;
 
-use voting::{Vote, Votes};
+use voting::{Status, Threshold, Vote, Votes};
 
 use crate::{
     error::ContractError,
     msg::{DepositInfo, ExecuteMsg, InstantiateMsg, QueryMsg},
-    proposal::{advance_proposal_id, Proposal, Status},
+    proposal::{advance_proposal_id, Proposal},
     query::ProposalListResponse,
     query::{ProposalResponse, VoteInfo, VoteListResponse, VoteResponse},
     state::{
         get_deposit_msg, get_return_deposit_msg, Ballot, Config, BALLOTS, CONFIG, PROPOSALS,
         PROPOSAL_COUNT, PROPOSAL_HOOKS, VOTE_HOOKS,
     },
-    threshold::Threshold,
     utils::{get_total_power, get_voting_power},
 };
 
diff --git a/contracts/cw-proposal-single/src/error.rs b/contracts/cw-proposal-single/src/error.rs
index c28fa436..e70de7e2 100644
--- a/contracts/cw-proposal-single/src/error.rs
+++ b/contracts/cw-proposal-single/src/error.rs
@@ -15,11 +15,8 @@ pub enum ContractError {
     #[error("Unauthorized")]
     Unauthorized {},
 
-    #[error("Required threshold cannot be zero")]
-    ZeroThreshold {},
-
-    #[error("Not possible to reach required (passing) threshold")]
-    UnreachableThreshold {},
+    #[error("{0}")]
+    ThresholdError(#[from] voting::ThresholdError),
 
     #[error("Suggested proposal expiration is larger than the maximum proposal duration")]
     InvalidExpiration {},
diff --git a/contracts/cw-proposal-single/src/lib.rs b/contracts/cw-proposal-single/src/lib.rs
index 80747ebd..eac98131 100644
--- a/contracts/cw-proposal-single/src/lib.rs
+++ b/contracts/cw-proposal-single/src/lib.rs
@@ -3,10 +3,14 @@ mod error;
 pub mod msg;
 pub mod proposal;
 pub mod query;
+#[cfg(test)]
+mod staking_tests;
+
 pub mod state;
+
 #[cfg(test)]
 mod tests;
-pub mod threshold;
+
 pub mod utils;
 
 pub use crate::error::ContractError;
diff --git a/contracts/cw-proposal-single/src/msg.rs b/contracts/cw-proposal-single/src/msg.rs
index dcaced42..973f25b1 100644
--- a/contracts/cw-proposal-single/src/msg.rs
+++ b/contracts/cw-proposal-single/src/msg.rs
@@ -3,8 +3,8 @@ use cw_utils::Duration;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 
-use crate::threshold::Threshold;
 use cw_core_macros::govmod_query;
+use voting::Threshold;
 
 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
 pub struct InstantiateMsg {
diff --git a/contracts/cw-proposal-single/src/proposal.rs b/contracts/cw-proposal-single/src/proposal.rs
index 95085218..2b04ab42 100644
--- a/contracts/cw-proposal-single/src/proposal.rs
+++ b/contracts/cw-proposal-single/src/proposal.rs
@@ -2,31 +2,13 @@ use cosmwasm_std::{Addr, BlockInfo, CosmosMsg, Decimal, Empty, StdResult, Storag
 use cw_utils::Expiration;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use voting::{compare_vote_count, VoteCmp, Votes};
+use voting::{compare_vote_count, PercentageThreshold, Status, Threshold, VoteCmp, Votes};
 
 use crate::{
     query::ProposalResponse,
     state::{CheckedDepositInfo, PROPOSAL_COUNT},
-    threshold::{PercentageThreshold, Threshold},
 };
 
-#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug, Copy)]
-#[serde(rename_all = "snake_case")]
-#[repr(u8)]
-pub enum Status {
-    /// The proposal is open for voting.
-    Open,
-    /// The proposal has been rejected.
-    Rejected,
-    /// The proposal has been passed but has not been executed.
-    Passed,
-    /// The proposal has been passed and executed.
-    Executed,
-    /// The proposal has failed or expired and has been closed. A
-    /// proposal deposit refund has been issued if applicable.
-    Closed,
-}
-
 #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
 pub struct Proposal {
     pub title: String,
@@ -257,18 +239,6 @@ impl Proposal {
     }
 }
 
-impl std::fmt::Display for Status {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            Status::Open => write!(f, "open"),
-            Status::Rejected => write!(f, "rejected"),
-            Status::Passed => write!(f, "passed"),
-            Status::Executed => write!(f, "executed"),
-            Status::Closed => write!(f, "closed"),
-        }
-    }
-}
-
 #[cfg(test)]
 mod test {
     use super::*;
diff --git a/contracts/cw-proposal-single/src/state.rs b/contracts/cw-proposal-single/src/state.rs
index d38a3671..48ed59fd 100644
--- a/contracts/cw-proposal-single/src/state.rs
+++ b/contracts/cw-proposal-single/src/state.rs
@@ -6,12 +6,11 @@ use indexable_hooks::Hooks;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 
-use voting::Vote;
+use voting::{Status, Threshold, Vote};
 
 use crate::{
     msg::{DepositInfo, DepositToken},
-    proposal::{Proposal, Status},
-    threshold::Threshold,
+    proposal::Proposal,
 };
 
 /// Counterpart to the `DepositInfo` struct which has been processed.
diff --git a/contracts/cw-proposal-sudo/Cargo.toml b/contracts/cw-proposal-sudo/Cargo.toml
index 037149ba..5f955a87 100644
--- a/contracts/cw-proposal-sudo/Cargo.toml
+++ b/contracts/cw-proposal-sudo/Cargo.toml
@@ -29,17 +29,17 @@ optimize = """docker run --rm -v "$(pwd)":/code \
 """
 
 [dependencies]
-cosmwasm-std = { version = "1.0.0-beta5" }
-cosmwasm-storage = { version = "1.0.0-beta5" }
-cw-storage-plus = "0.12"
-cw2 = "0.12"
+cosmwasm-std = { version = "1.0.0-beta" }
+cosmwasm-storage = { version = "1.0.0-beta" }
+cw-storage-plus = "0.13"
+cw2 = "0.13"
 schemars = "0.8"
 serde = { version = "1.0", default-features = false, features = ["derive"] }
 thiserror = { version = "1.0" }
-cw-core = { path = "../cw-core", version = "0.1.0", features = ["library"] }
-cw-core-macros = { version = "0.1.0", path = "../../packages/cw-core-macros" } 
-cw-core-interface = { version = "0.1.0", path = "../../packages/cw-core-interface" }
+cw-core = { path = "../cw-core", version = "*", features = ["library"] }
+cw-core-macros = { version = "*", path = "../../packages/cw-core-macros" }
+cw-core-interface = { version = "*", path = "../../packages/cw-core-interface" }
 
 [dev-dependencies]
-cosmwasm-schema = { version = "1.0.0-beta5" }
-cw-multi-test = "0.12"
+cosmwasm-schema = { version = "1.0.0-beta" }
+cw-multi-test = "0.13"
diff --git a/contracts/cw20-balance-voting/Cargo.toml b/contracts/cw20-balance-voting/Cargo.toml
index e2f2245b..5df33b0f 100644
--- a/contracts/cw20-balance-voting/Cargo.toml
+++ b/contracts/cw20-balance-voting/Cargo.toml
@@ -29,19 +29,19 @@ optimize = """docker run --rm -v "$(pwd)":/code \
 """
 
 [dependencies]
-cosmwasm-std = { version = "1.0.0-beta5" }
-cosmwasm-storage = { version = "1.0.0-beta5" }
-cw-storage-plus = "0.12"
-cw2 = "0.12"
-cw20 = "0.12"
-cw-utils = "0.12"
+cosmwasm-std = { version = "1.0.0-beta" }
+cosmwasm-storage = { version = "1.0.0-beta" }
+cw-storage-plus = "0.13"
+cw2 = "0.13"
+cw20 = "0.13"
+cw-utils = "0.13"
 schemars = "0.8"
 serde = { version = "1.0", default-features = false, features = ["derive"] }
 thiserror = { version = "1.0" }
 cw-core-macros = { version = "0.1.0", path = "../../packages/cw-core-macros" }
-cw-core-interface = { version = "0.1.0", path = "../../packages/cw-core-interface" } 
-cw20-base = {  version = "0.12", features = ["library"] }
+cw-core-interface = { version = "0.1.0", path = "../../packages/cw-core-interface" }
+cw20-base = {  version = "0.13", features = ["library"] }
 
 [dev-dependencies]
-cosmwasm-schema = { version = "1.0.0-beta5" }
-cw-multi-test = "0.12"
+cosmwasm-schema = { version = "1.0.0-beta" }
+cw-multi-test = "0.13"
diff --git a/contracts/cw20-staked-balance-voting/Cargo.toml b/contracts/cw20-staked-balance-voting/Cargo.toml
index c41f2e5a..891432ba 100644
--- a/contracts/cw20-staked-balance-voting/Cargo.toml
+++ b/contracts/cw20-staked-balance-voting/Cargo.toml
@@ -29,20 +29,20 @@ optimize = """docker run --rm -v "$(pwd)":/code \
 """
 
 [dependencies]
-cosmwasm-std = { version = "1.0.0-beta5" }
-cosmwasm-storage = { version = "1.0.0-beta5" }
-cw-storage-plus = "0.12"
-cw2 = "0.12"
-cw20 = "0.12"
-cw-utils = "0.11"
+cosmwasm-std = { version = "1.0.0-beta" }
+cosmwasm-storage = { version = "1.0.0-beta" }
+cw-storage-plus = "0.13"
+cw2 = "0.13"
+cw20 = "0.13"
+cw-utils = "0.13"
 schemars = "0.8"
 serde = { version = "1.0", default-features = false, features = ["derive"] }
 thiserror = { version = "1.0" }
-cw-core-macros = { version = "0.1.0", path = "../../packages/cw-core-macros" }
-cw-core-interface = { version = "0.1.0", path = "../../packages/cw-core-interface" }  
+cw-core-macros = { version = "*", path = "../../packages/cw-core-macros" }
+cw-core-interface = { version = "*", path = "../../packages/cw-core-interface" }
 stake-cw20 = { path = "../stake-cw20" }
-cw20-base = {  version = "0.12", features = ["library"] }
+cw20-base = {  version = "0.13", features = ["library"] }
 
 [dev-dependencies]
-cosmwasm-schema = { version = "1.0.0-beta5" }
-cw-multi-test = "0.12"
+cosmwasm-schema = { version = "1.0.0-beta" }
+cw-multi-test = "0.13"
diff --git a/contracts/cw4-voting/Cargo.toml b/contracts/cw4-voting/Cargo.toml
index a036f845..f8dfae3b 100644
--- a/contracts/cw4-voting/Cargo.toml
+++ b/contracts/cw4-voting/Cargo.toml
@@ -29,19 +29,19 @@ optimize = """docker run --rm -v "$(pwd)":/code \
 """
 
 [dependencies]
-cosmwasm-std = { version = "1.0.0-beta5" }
-cosmwasm-storage = { version = "1.0.0-beta5" }
-cw-storage-plus = "0.12"
-cw2 = "0.12"
-cw-utils = "0.12"
+cosmwasm-std = { version = "1.0.0-beta" }
+cosmwasm-storage = { version = "1.0.0-beta" }
+cw-storage-plus = "0.13"
+cw2 = "0.13"
+cw-utils = "0.13"
 schemars = "0.8"
 serde = { version = "1.0", default-features = false, features = ["derive"] }
 thiserror = { version = "1.0" }
 cw-core-macros = { version = "0.1.0", path = "../../packages/cw-core-macros" }
-cw-core-interface = { version = "0.1.0", path = "../../packages/cw-core-interface" } 
-cw4-group = "0.11"
-cw4 = "0.11"
+cw-core-interface = { version = "0.1.0", path = "../../packages/cw-core-interface" }
+cw4 = "0.13"
+cw4-group = "0.13"
 
 [dev-dependencies]
-cosmwasm-schema = { version = "1.0.0-beta5" }
-cw-multi-test = "0.12"
+cosmwasm-schema = { version = "1.0.0-beta" }
+cw-multi-test = "0.13"
diff --git a/contracts/cw4-voting/src/contract.rs b/contracts/cw4-voting/src/contract.rs
index e35f6c3c..d6cffca5 100644
--- a/contracts/cw4-voting/src/contract.rs
+++ b/contracts/cw4-voting/src/contract.rs
@@ -52,6 +52,7 @@ pub fn instantiate(
         funds: vec![],
         label: env.contract.address.to_string(),
     };
+
     let msg = SubMsg::reply_on_success(msg, INSTANTIATE_GROUP_REPLY_ID);
 
     DAO_ADDRESS.save(deps.storage, &info.sender)?;
diff --git a/contracts/proposal-hooks-counter/Cargo.toml b/contracts/proposal-hooks-counter/Cargo.toml
index 070b3eb1..fd5f0f68 100644
--- a/contracts/proposal-hooks-counter/Cargo.toml
+++ b/contracts/proposal-hooks-counter/Cargo.toml
@@ -29,10 +29,10 @@ optimize = """docker run --rm -v "$(pwd)":/code \
 """
 
 [dependencies]
-cosmwasm-std = { version = "1.0.0-beta5" }
-cosmwasm-storage = { version = "1.0.0-beta5" }
-cw-storage-plus = "0.12"
-cw2 = "0.12"
+cosmwasm-std = { version = "1.0.0-beta" }
+cosmwasm-storage = { version = "1.0.0-beta" }
+cw-storage-plus = "0.13"
+cw2 = "0.13"
 schemars = "0.8"
 serde = { version = "1.0", default-features = false, features = ["derive"] }
 thiserror = { version = "1.0" }
@@ -41,12 +41,12 @@ vote-hooks = { version = "0.1.0", path = "../../packages/vote-hooks" }
 
 [dev-dependencies]
 indexable-hooks = { version = "0.1.0", path = "../../packages/indexable-hooks" }
-cw20 = "0.12"
+cw20 = "0.13"
 cw20-balance-voting = { path = "../cw20-balance-voting", version = "0.1.0" }
-cw20-base = "0.12"
-cw-utils = "0.12"
+cw20-base = "0.13"
+cw-utils = "0.13"
 voting = { version = "0.1.0", path = "../../packages/voting" }
 cw-core = { path = "../cw-core", version = "0.1.0", features = ["library"] }
 cw-proposal-single = { path = "../cw-proposal-single" }
-cosmwasm-schema = { version = "1.0.0-beta5" }
-cw-multi-test = "0.12"
+cosmwasm-schema = { version = "1.0.0-beta" }
+cw-multi-test = "0.13"
diff --git a/contracts/stake-cw20-external-rewards/Cargo.toml b/contracts/stake-cw20-external-rewards/Cargo.toml
index 16d07284..122af1d0 100644
--- a/contracts/stake-cw20-external-rewards/Cargo.toml
+++ b/contracts/stake-cw20-external-rewards/Cargo.toml
@@ -18,12 +18,12 @@ library = []
 [dependencies]
 cosmwasm-std = { version = "1.0.0-beta" }
 cosmwasm-storage = { version = "1.0.0-beta" }
-cw-storage-plus = { version = "0.11" }
-cw-controllers = "0.11"
-cw20 = { version = "0.11" }
-cw-utils = { version = "0.11" }
-cw20-base = {  version = "0.11", features = ["library"] }
-cw2 = "0.11"
+cw-storage-plus = { version = "0.13" }
+cw-controllers = "0.13"
+cw20 = { version = "0.13" }
+cw-utils = { version = "0.13" }
+cw20-base = {  version = "0.13", features = ["library"] }
+cw2 = "0.13"
 schemars = "0.8.8"
 serde = { version = "1.0.132", default-features = false, features = ["derive"] }
 thiserror = { version = "1.0.30" }
@@ -31,5 +31,5 @@ stake-cw20 = { path = "../stake-cw20", features = ["library"]}
 
 [dev-dependencies]
 cosmwasm-schema = { version = "1.0.0-beta" }
-cw-multi-test = {  version = "0.11" }
+cw-multi-test = {  version = "0.13" }
 anyhow = { version = "1.0.51"}
diff --git a/contracts/stake-cw20/Cargo.toml b/contracts/stake-cw20/Cargo.toml
index 95674f28..2e92d081 100644
--- a/contracts/stake-cw20/Cargo.toml
+++ b/contracts/stake-cw20/Cargo.toml
@@ -18,17 +18,17 @@ library = []
 [dependencies]
 cosmwasm-std = { version = "1.0.0-beta" }
 cosmwasm-storage = { version = "1.0.0-beta" }
-cw-storage-plus = { version = "0.11" }
-cw-controllers = "0.11"
-cw20 = { version = "0.11" }
-cw-utils = { version = "0.11" }
-cw20-base = {  version = "0.11", features = ["library"] }
-cw2 = "0.11"
+cw-storage-plus = { version = "0.13" }
+cw-controllers = "0.13"
+cw20 = { version = "0.13" }
+cw-utils = { version = "0.13" }
+cw20-base = {  version = "0.13", features = ["library"] }
+cw2 = "0.13"
 schemars = "0.8.8"
 serde = { version = "1.0.132", default-features = false, features = ["derive"] }
 thiserror = { version = "1.0.30" }
 
 [dev-dependencies]
 cosmwasm-schema = { version = "1.0.0-beta" }
-cw-multi-test = {  version = "0.11" }
+cw-multi-test = {  version = "0.13" }
 anyhow = { version = "1.0.51"}
diff --git a/packages/cw-core-interface/Cargo.toml b/packages/cw-core-interface/Cargo.toml
index c837470e..2cbd437f 100644
--- a/packages/cw-core-interface/Cargo.toml
+++ b/packages/cw-core-interface/Cargo.toml
@@ -10,7 +10,7 @@ cosmwasm-std = { version = "1.0.0-beta5" }
 cw-core-macros = { version = "0.1.0", path = "../../packages/cw-core-macros" } 
 serde = { version = "1.0", default-features = false, features = ["derive"] }
 schemars = "0.8"
-cw2 = "0.12"
+cw2 = "0.13"
 
 [dev-dependencies]
 cosmwasm-schema = { version = "1.0.0-beta5" }
diff --git a/packages/indexable-hooks/Cargo.toml b/packages/indexable-hooks/Cargo.toml
index ae280c2e..bc9763f9 100644
--- a/packages/indexable-hooks/Cargo.toml
+++ b/packages/indexable-hooks/Cargo.toml
@@ -9,5 +9,5 @@ edition = "2021"
 schemars = "0.8"
 serde = { version = "1.0", default-features = false, features = ["derive"] }
 thiserror = { version = "1.0" }
-cosmwasm-std = "1.0.0-beta5"
-cw-storage-plus = "0.12"
+cosmwasm-std = "1.0.0-beta"
+cw-storage-plus = "0.13"
diff --git a/packages/proposal-hooks/Cargo.toml b/packages/proposal-hooks/Cargo.toml
index 4c68f159..d13a4e7f 100644
--- a/packages/proposal-hooks/Cargo.toml
+++ b/packages/proposal-hooks/Cargo.toml
@@ -8,5 +8,5 @@ edition = "2021"
 [dependencies]
 schemars = "0.8"
 serde = { version = "1.0", default-features = false, features = ["derive"] }
-cosmwasm-std = "1.0.0-beta5"
+cosmwasm-std = "1.0.0-beta"
 indexable-hooks = { version = "0.1.0", path = "../indexable-hooks" }
\ No newline at end of file
diff --git a/packages/vote-hooks/Cargo.toml b/packages/vote-hooks/Cargo.toml
index 615eb565..4381535b 100644
--- a/packages/vote-hooks/Cargo.toml
+++ b/packages/vote-hooks/Cargo.toml
@@ -8,5 +8,5 @@ edition = "2021"
 [dependencies]
 schemars = "0.8"
 serde = { version = "1.0", default-features = false, features = ["derive"] }
-cosmwasm-std = "1.0.0-beta5"
+cosmwasm-std = "1.0.0-beta"
 indexable-hooks = { version = "0.1.0", path = "../indexable-hooks" }
\ No newline at end of file
diff --git a/packages/voting/Cargo.toml b/packages/voting/Cargo.toml
index cab1eb1b..6d29ff34 100644
--- a/packages/voting/Cargo.toml
+++ b/packages/voting/Cargo.toml
@@ -6,6 +6,7 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-cosmwasm-std = { version = "1.0.0-beta5" }
+cosmwasm-std = { version = "1.0.0-beta" }
 schemars = "0.8"
 serde = { version = "1.0", default-features = false, features = ["derive"] }
+thiserror = { version = "1.0" }
\ No newline at end of file
diff --git a/packages/voting/src/lib.rs b/packages/voting/src/lib.rs
index 184d2213..c5cc8c71 100644
--- a/packages/voting/src/lib.rs
+++ b/packages/voting/src/lib.rs
@@ -1,351 +1,5 @@
-use cosmwasm_std::{Decimal, Uint128, Uint256};
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
+pub mod status;
+pub mod threshold;
+pub mod voting;
 
-// We multiply by this when calculating needed_votes in order to round
-// up properly.
-const PRECISION_FACTOR: u128 = 10u128.pow(9);
-
-#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
-pub struct Votes {
-    pub yes: Uint128,
-    pub no: Uint128,
-    pub abstain: Uint128,
-}
-
-#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, JsonSchema, Debug)]
-#[serde(rename_all = "lowercase")]
-#[repr(u8)]
-pub enum Vote {
-    /// Marks support for the proposal.
-    Yes,
-    /// Marks opposition to the proposal.
-    No,
-    /// Marks participation but does not count towards the ratio of
-    /// support / opposed.
-    Abstain,
-}
-
-pub enum VoteCmp {
-    Greater,
-    Geq,
-}
-
-/// Compares `votes` with `total_power * passing_percentage`. The
-/// comparason function used depends on the `VoteCmp` variation
-/// selected.
-///
-/// !!NOTE!! THIS FUNCTION DOES NOT ROUND UP.
-///
-/// For example, the following assertion will succede:
-///
-/// ```rust
-/// use voting::{compare_vote_count, VoteCmp};
-/// use cosmwasm_std::{Uint128, Decimal};
-/// fn test() {
-///     assert!(compare_vote_count(
-///         Uint128::new(7),
-///         VoteCmp::Greater,
-///         Uint128::new(13),
-///         Decimal::from_ratio(7u64, 13u64)
-///     ));
-/// }
-/// ```
-///
-/// This is because `7 * (7/13)` is `6.999...` after rounding. You
-/// MUST ensure this is the behavior you want when calling this
-/// function.
-///
-/// For our current purposes this is OK as the only place we use the
-/// `Greater` comparason is when looking to see if no votes have
-/// reached `> (1 - threshold)` and thus made the proposal
-/// unpassable. As threshold will be a rounded down version of the
-/// infinite percision real version `1 - threshold` will actually be a
-/// higher magnitured than the real one meaning that we won't ever be
-/// in the position of simeltaniously reporting a proposal as both
-/// rejected and passed.
-///
-pub fn compare_vote_count(
-    votes: Uint128,
-    cmp: VoteCmp,
-    total_power: Uint128,
-    passing_percentage: Decimal,
-) -> bool {
-    let votes = votes.full_mul(PRECISION_FACTOR);
-    let total_power = total_power.full_mul(PRECISION_FACTOR);
-    let threshold = total_power.multiply_ratio(
-        passing_percentage.atomics(),
-        Uint256::from(10u64).pow(passing_percentage.decimal_places()),
-    );
-    match cmp {
-        VoteCmp::Greater => votes > threshold,
-        VoteCmp::Geq => votes >= threshold,
-    }
-}
-
-impl Votes {
-    /// Constructs an zero'd out votes struct.
-    pub fn zero() -> Self {
-        Self {
-            yes: Uint128::zero(),
-            no: Uint128::zero(),
-            abstain: Uint128::zero(),
-        }
-    }
-
-    /// Constructs a vote with a specified number of yes votes. Used
-    /// for testing.
-    #[cfg(test)]
-    pub fn with_yes(yes: Uint128) -> Self {
-        Self {
-            yes,
-            no: Uint128::zero(),
-            abstain: Uint128::zero(),
-        }
-    }
-
-    /// Adds a vote to the votes.
-    pub fn add_vote(&mut self, vote: Vote, power: Uint128) {
-        match vote {
-            Vote::Yes => self.yes += power,
-            Vote::No => self.no += power,
-            Vote::Abstain => self.abstain += power,
-        }
-    }
-
-    /// Computes the total number of votes cast.
-    ///
-    /// NOTE: The total number of votes avaliable from a voting module
-    /// is a `Uint128`. As it is not possible to vote twice we know
-    /// that the sum of votes must be <= 2^128 and can safely return a
-    /// `Uint128` from this function. A missbehaving voting power
-    /// module may break this invariant.
-    pub fn total(&self) -> Uint128 {
-        self.yes + self.no + self.abstain
-    }
-}
-
-impl std::fmt::Display for Vote {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            Vote::Yes => write!(f, "yes"),
-            Vote::No => write!(f, "no"),
-            Vote::Abstain => write!(f, "abstain"),
-        }
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use super::*;
-
-    #[test]
-    fn count_votes() {
-        let mut votes = Votes::with_yes(Uint128::new(5));
-        votes.add_vote(Vote::No, Uint128::new(10));
-        votes.add_vote(Vote::Yes, Uint128::new(30));
-        votes.add_vote(Vote::Abstain, Uint128::new(40));
-
-        assert_eq!(votes.total(), Uint128::new(5 + 10 + 30 + 40));
-        assert_eq!(votes.yes, Uint128::new(35));
-        assert_eq!(votes.no, Uint128::new(10));
-        assert_eq!(votes.abstain, Uint128::new(40));
-    }
-
-    #[test]
-    fn vote_comparasons() {
-        assert!(!compare_vote_count(
-            Uint128::new(7),
-            VoteCmp::Geq,
-            Uint128::new(15),
-            Decimal::percent(50)
-        ));
-        assert!(!compare_vote_count(
-            Uint128::new(7),
-            VoteCmp::Greater,
-            Uint128::new(15),
-            Decimal::percent(50)
-        ));
-
-        assert!(compare_vote_count(
-            Uint128::new(7),
-            VoteCmp::Geq,
-            Uint128::new(14),
-            Decimal::percent(50)
-        ));
-        assert!(!compare_vote_count(
-            Uint128::new(7),
-            VoteCmp::Greater,
-            Uint128::new(14),
-            Decimal::percent(50)
-        ));
-
-        assert!(compare_vote_count(
-            Uint128::new(7),
-            VoteCmp::Geq,
-            Uint128::new(13),
-            Decimal::from_ratio(7u64, 13u64)
-        ));
-
-        assert!(!compare_vote_count(
-            Uint128::new(6),
-            VoteCmp::Greater,
-            Uint128::new(13),
-            Decimal::one() - Decimal::from_ratio(7u64, 13u64)
-        ));
-        assert!(compare_vote_count(
-            Uint128::new(7),
-            VoteCmp::Greater,
-            Uint128::new(13),
-            Decimal::from_ratio(7u64, 13u64)
-        ));
-
-        assert!(!compare_vote_count(
-            Uint128::new(4),
-            VoteCmp::Geq,
-            Uint128::new(9),
-            Decimal::percent(50)
-        ))
-    }
-
-    #[test]
-    fn more_votes_tests() {
-        assert!(compare_vote_count(
-            Uint128::new(1),
-            VoteCmp::Geq,
-            Uint128::new(3),
-            Decimal::permille(333)
-        ));
-
-        assert!(!compare_vote_count(
-            Uint128::new(1),
-            VoteCmp::Geq,
-            Uint128::new(3),
-            Decimal::permille(334)
-        ));
-        assert!(compare_vote_count(
-            Uint128::new(2),
-            VoteCmp::Geq,
-            Uint128::new(3),
-            Decimal::permille(334)
-        ));
-
-        assert!(compare_vote_count(
-            Uint128::new(11),
-            VoteCmp::Geq,
-            Uint128::new(30),
-            Decimal::permille(333)
-        ));
-
-        assert!(compare_vote_count(
-            Uint128::new(15),
-            VoteCmp::Geq,
-            Uint128::new(30),
-            Decimal::permille(500)
-        ));
-        assert!(!compare_vote_count(
-            Uint128::new(15),
-            VoteCmp::Greater,
-            Uint128::new(30),
-            Decimal::permille(500)
-        ));
-
-        assert!(compare_vote_count(
-            Uint128::new(0),
-            VoteCmp::Geq,
-            Uint128::new(0),
-            Decimal::permille(500)
-        ));
-        assert!(!compare_vote_count(
-            Uint128::new(0),
-            VoteCmp::Greater,
-            Uint128::new(0),
-            Decimal::permille(500)
-        ));
-
-        assert!(!compare_vote_count(
-            Uint128::new(0),
-            VoteCmp::Geq,
-            Uint128::new(1),
-            Decimal::permille(1)
-        ));
-        assert!(!compare_vote_count(
-            Uint128::new(0),
-            VoteCmp::Greater,
-            Uint128::new(1),
-            Decimal::permille(1)
-        ));
-
-        assert!(compare_vote_count(
-            Uint128::new(1),
-            VoteCmp::Geq,
-            Uint128::new(1),
-            Decimal::permille(1)
-        ));
-        assert!(compare_vote_count(
-            Uint128::new(1),
-            VoteCmp::Greater,
-            Uint128::new(1),
-            Decimal::permille(1)
-        ));
-
-        assert!(!compare_vote_count(
-            Uint128::new(0),
-            VoteCmp::Geq,
-            Uint128::new(1),
-            Decimal::permille(999)
-        ));
-        assert!(!compare_vote_count(
-            Uint128::new(0),
-            VoteCmp::Greater,
-            Uint128::new(1),
-            Decimal::permille(999)
-        ));
-    }
-
-    #[test]
-    fn tricky_vote_counts() {
-        let threshold = Decimal::percent(50);
-        for count in 1..50_000 {
-            assert!(compare_vote_count(
-                Uint128::new(count),
-                VoteCmp::Geq,
-                Uint128::new(count * 2),
-                threshold
-            ));
-            assert!(!compare_vote_count(
-                Uint128::new(count),
-                VoteCmp::Greater,
-                Uint128::new(count * 2),
-                threshold
-            ))
-        }
-
-        // Zero votes out of zero total power meet any threshold. When
-        // Geq is used. Always fail otherwise.
-        assert!(compare_vote_count(
-            Uint128::zero(),
-            VoteCmp::Geq,
-            Uint128::new(1),
-            Decimal::percent(0)
-        ));
-        assert!(compare_vote_count(
-            Uint128::zero(),
-            VoteCmp::Geq,
-            Uint128::new(0),
-            Decimal::percent(0)
-        ));
-        assert!(!compare_vote_count(
-            Uint128::zero(),
-            VoteCmp::Greater,
-            Uint128::new(1),
-            Decimal::percent(0)
-        ));
-        assert!(!compare_vote_count(
-            Uint128::zero(),
-            VoteCmp::Greater,
-            Uint128::new(0),
-            Decimal::percent(0)
-        ))
-    }
-}
+pub use crate::{status::*, threshold::*, voting::*};
diff --git a/packages/voting/src/status.rs b/packages/voting/src/status.rs
new file mode 100644
index 00000000..97f02efc
--- /dev/null
+++ b/packages/voting/src/status.rs
@@ -0,0 +1,31 @@
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug, Copy)]
+#[serde(rename_all = "snake_case")]
+#[repr(u8)]
+pub enum Status {
+    /// The proposal is open for voting.
+    Open,
+    /// The proposal has been rejected.
+    Rejected,
+    /// The proposal has been passed but has not been executed.
+    Passed,
+    /// The proposal has been passed and executed.
+    Executed,
+    /// The proposal has failed or expired and has been closed. A
+    /// proposal deposit refund has been issued if applicable.
+    Closed,
+}
+
+impl std::fmt::Display for Status {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Status::Open => write!(f, "open"),
+            Status::Rejected => write!(f, "rejected"),
+            Status::Passed => write!(f, "passed"),
+            Status::Executed => write!(f, "executed"),
+            Status::Closed => write!(f, "closed"),
+        }
+    }
+}
diff --git a/contracts/cw-proposal-single/src/threshold.rs b/packages/voting/src/threshold.rs
similarity index 86%
rename from contracts/cw-proposal-single/src/threshold.rs
rename to packages/voting/src/threshold.rs
index d01c30a0..20a3eab6 100644
--- a/contracts/cw-proposal-single/src/threshold.rs
+++ b/packages/voting/src/threshold.rs
@@ -2,7 +2,16 @@ use cosmwasm_std::Decimal;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 
-use crate::ContractError;
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum ThresholdError {
+    #[error("Required threshold cannot be zero")]
+    ZeroThreshold {},
+
+    #[error("Not possible to reach required (passing) threshold")]
+    UnreachableThreshold {},
+}
 
 /// A percentage of voting power that must vote yes for a proposal to
 /// pass. An example of why this is needed:
@@ -58,12 +67,12 @@ pub enum Threshold {
 }
 
 /// Asserts that the 0.0 < percent <= 1.0
-fn validate_percentage(percent: &PercentageThreshold) -> Result<(), ContractError> {
+fn validate_percentage(percent: &PercentageThreshold) -> Result<(), ThresholdError> {
     if let PercentageThreshold::Percent(percent) = percent {
         if percent.is_zero() {
-            Err(ContractError::ZeroThreshold {})
+            Err(ThresholdError::ZeroThreshold {})
         } else if *percent > Decimal::one() {
-            Err(ContractError::UnreachableThreshold {})
+            Err(ThresholdError::UnreachableThreshold {})
         } else {
             Ok(())
         }
@@ -73,10 +82,10 @@ fn validate_percentage(percent: &PercentageThreshold) -> Result<(), ContractErro
 }
 
 /// Asserts that a quorum <= 1. Quorums may be zero.
-fn validate_quorum(quorum: &PercentageThreshold) -> Result<(), ContractError> {
+fn validate_quorum(quorum: &PercentageThreshold) -> Result<(), ThresholdError> {
     if let PercentageThreshold::Percent(quorum) = quorum {
         if *quorum > Decimal::one() {
-            Err(ContractError::UnreachableThreshold {})
+            Err(ThresholdError::UnreachableThreshold {})
         } else {
             Ok(())
         }
@@ -88,7 +97,7 @@ fn validate_quorum(quorum: &PercentageThreshold) -> Result<(), ContractError> {
 impl Threshold {
     /// returns error if this is an unreachable value,
     /// given a total weight of all members in the group
-    pub fn validate(&self) -> Result<(), ContractError> {
+    pub fn validate(&self) -> Result<(), ThresholdError> {
         match self {
             Threshold::AbsolutePercentage {
                 percentage: percentage_needed,
diff --git a/packages/voting/src/voting.rs b/packages/voting/src/voting.rs
new file mode 100644
index 00000000..184d2213
--- /dev/null
+++ b/packages/voting/src/voting.rs
@@ -0,0 +1,351 @@
+use cosmwasm_std::{Decimal, Uint128, Uint256};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+
+// We multiply by this when calculating needed_votes in order to round
+// up properly.
+const PRECISION_FACTOR: u128 = 10u128.pow(9);
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
+pub struct Votes {
+    pub yes: Uint128,
+    pub no: Uint128,
+    pub abstain: Uint128,
+}
+
+#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, JsonSchema, Debug)]
+#[serde(rename_all = "lowercase")]
+#[repr(u8)]
+pub enum Vote {
+    /// Marks support for the proposal.
+    Yes,
+    /// Marks opposition to the proposal.
+    No,
+    /// Marks participation but does not count towards the ratio of
+    /// support / opposed.
+    Abstain,
+}
+
+pub enum VoteCmp {
+    Greater,
+    Geq,
+}
+
+/// Compares `votes` with `total_power * passing_percentage`. The
+/// comparason function used depends on the `VoteCmp` variation
+/// selected.
+///
+/// !!NOTE!! THIS FUNCTION DOES NOT ROUND UP.
+///
+/// For example, the following assertion will succede:
+///
+/// ```rust
+/// use voting::{compare_vote_count, VoteCmp};
+/// use cosmwasm_std::{Uint128, Decimal};
+/// fn test() {
+///     assert!(compare_vote_count(
+///         Uint128::new(7),
+///         VoteCmp::Greater,
+///         Uint128::new(13),
+///         Decimal::from_ratio(7u64, 13u64)
+///     ));
+/// }
+/// ```
+///
+/// This is because `7 * (7/13)` is `6.999...` after rounding. You
+/// MUST ensure this is the behavior you want when calling this
+/// function.
+///
+/// For our current purposes this is OK as the only place we use the
+/// `Greater` comparason is when looking to see if no votes have
+/// reached `> (1 - threshold)` and thus made the proposal
+/// unpassable. As threshold will be a rounded down version of the
+/// infinite percision real version `1 - threshold` will actually be a
+/// higher magnitured than the real one meaning that we won't ever be
+/// in the position of simeltaniously reporting a proposal as both
+/// rejected and passed.
+///
+pub fn compare_vote_count(
+    votes: Uint128,
+    cmp: VoteCmp,
+    total_power: Uint128,
+    passing_percentage: Decimal,
+) -> bool {
+    let votes = votes.full_mul(PRECISION_FACTOR);
+    let total_power = total_power.full_mul(PRECISION_FACTOR);
+    let threshold = total_power.multiply_ratio(
+        passing_percentage.atomics(),
+        Uint256::from(10u64).pow(passing_percentage.decimal_places()),
+    );
+    match cmp {
+        VoteCmp::Greater => votes > threshold,
+        VoteCmp::Geq => votes >= threshold,
+    }
+}
+
+impl Votes {
+    /// Constructs an zero'd out votes struct.
+    pub fn zero() -> Self {
+        Self {
+            yes: Uint128::zero(),
+            no: Uint128::zero(),
+            abstain: Uint128::zero(),
+        }
+    }
+
+    /// Constructs a vote with a specified number of yes votes. Used
+    /// for testing.
+    #[cfg(test)]
+    pub fn with_yes(yes: Uint128) -> Self {
+        Self {
+            yes,
+            no: Uint128::zero(),
+            abstain: Uint128::zero(),
+        }
+    }
+
+    /// Adds a vote to the votes.
+    pub fn add_vote(&mut self, vote: Vote, power: Uint128) {
+        match vote {
+            Vote::Yes => self.yes += power,
+            Vote::No => self.no += power,
+            Vote::Abstain => self.abstain += power,
+        }
+    }
+
+    /// Computes the total number of votes cast.
+    ///
+    /// NOTE: The total number of votes avaliable from a voting module
+    /// is a `Uint128`. As it is not possible to vote twice we know
+    /// that the sum of votes must be <= 2^128 and can safely return a
+    /// `Uint128` from this function. A missbehaving voting power
+    /// module may break this invariant.
+    pub fn total(&self) -> Uint128 {
+        self.yes + self.no + self.abstain
+    }
+}
+
+impl std::fmt::Display for Vote {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Vote::Yes => write!(f, "yes"),
+            Vote::No => write!(f, "no"),
+            Vote::Abstain => write!(f, "abstain"),
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn count_votes() {
+        let mut votes = Votes::with_yes(Uint128::new(5));
+        votes.add_vote(Vote::No, Uint128::new(10));
+        votes.add_vote(Vote::Yes, Uint128::new(30));
+        votes.add_vote(Vote::Abstain, Uint128::new(40));
+
+        assert_eq!(votes.total(), Uint128::new(5 + 10 + 30 + 40));
+        assert_eq!(votes.yes, Uint128::new(35));
+        assert_eq!(votes.no, Uint128::new(10));
+        assert_eq!(votes.abstain, Uint128::new(40));
+    }
+
+    #[test]
+    fn vote_comparasons() {
+        assert!(!compare_vote_count(
+            Uint128::new(7),
+            VoteCmp::Geq,
+            Uint128::new(15),
+            Decimal::percent(50)
+        ));
+        assert!(!compare_vote_count(
+            Uint128::new(7),
+            VoteCmp::Greater,
+            Uint128::new(15),
+            Decimal::percent(50)
+        ));
+
+        assert!(compare_vote_count(
+            Uint128::new(7),
+            VoteCmp::Geq,
+            Uint128::new(14),
+            Decimal::percent(50)
+        ));
+        assert!(!compare_vote_count(
+            Uint128::new(7),
+            VoteCmp::Greater,
+            Uint128::new(14),
+            Decimal::percent(50)
+        ));
+
+        assert!(compare_vote_count(
+            Uint128::new(7),
+            VoteCmp::Geq,
+            Uint128::new(13),
+            Decimal::from_ratio(7u64, 13u64)
+        ));
+
+        assert!(!compare_vote_count(
+            Uint128::new(6),
+            VoteCmp::Greater,
+            Uint128::new(13),
+            Decimal::one() - Decimal::from_ratio(7u64, 13u64)
+        ));
+        assert!(compare_vote_count(
+            Uint128::new(7),
+            VoteCmp::Greater,
+            Uint128::new(13),
+            Decimal::from_ratio(7u64, 13u64)
+        ));
+
+        assert!(!compare_vote_count(
+            Uint128::new(4),
+            VoteCmp::Geq,
+            Uint128::new(9),
+            Decimal::percent(50)
+        ))
+    }
+
+    #[test]
+    fn more_votes_tests() {
+        assert!(compare_vote_count(
+            Uint128::new(1),
+            VoteCmp::Geq,
+            Uint128::new(3),
+            Decimal::permille(333)
+        ));
+
+        assert!(!compare_vote_count(
+            Uint128::new(1),
+            VoteCmp::Geq,
+            Uint128::new(3),
+            Decimal::permille(334)
+        ));
+        assert!(compare_vote_count(
+            Uint128::new(2),
+            VoteCmp::Geq,
+            Uint128::new(3),
+            Decimal::permille(334)
+        ));
+
+        assert!(compare_vote_count(
+            Uint128::new(11),
+            VoteCmp::Geq,
+            Uint128::new(30),
+            Decimal::permille(333)
+        ));
+
+        assert!(compare_vote_count(
+            Uint128::new(15),
+            VoteCmp::Geq,
+            Uint128::new(30),
+            Decimal::permille(500)
+        ));
+        assert!(!compare_vote_count(
+            Uint128::new(15),
+            VoteCmp::Greater,
+            Uint128::new(30),
+            Decimal::permille(500)
+        ));
+
+        assert!(compare_vote_count(
+            Uint128::new(0),
+            VoteCmp::Geq,
+            Uint128::new(0),
+            Decimal::permille(500)
+        ));
+        assert!(!compare_vote_count(
+            Uint128::new(0),
+            VoteCmp::Greater,
+            Uint128::new(0),
+            Decimal::permille(500)
+        ));
+
+        assert!(!compare_vote_count(
+            Uint128::new(0),
+            VoteCmp::Geq,
+            Uint128::new(1),
+            Decimal::permille(1)
+        ));
+        assert!(!compare_vote_count(
+            Uint128::new(0),
+            VoteCmp::Greater,
+            Uint128::new(1),
+            Decimal::permille(1)
+        ));
+
+        assert!(compare_vote_count(
+            Uint128::new(1),
+            VoteCmp::Geq,
+            Uint128::new(1),
+            Decimal::permille(1)
+        ));
+        assert!(compare_vote_count(
+            Uint128::new(1),
+            VoteCmp::Greater,
+            Uint128::new(1),
+            Decimal::permille(1)
+        ));
+
+        assert!(!compare_vote_count(
+            Uint128::new(0),
+            VoteCmp::Geq,
+            Uint128::new(1),
+            Decimal::permille(999)
+        ));
+        assert!(!compare_vote_count(
+            Uint128::new(0),
+            VoteCmp::Greater,
+            Uint128::new(1),
+            Decimal::permille(999)
+        ));
+    }
+
+    #[test]
+    fn tricky_vote_counts() {
+        let threshold = Decimal::percent(50);
+        for count in 1..50_000 {
+            assert!(compare_vote_count(
+                Uint128::new(count),
+                VoteCmp::Geq,
+                Uint128::new(count * 2),
+                threshold
+            ));
+            assert!(!compare_vote_count(
+                Uint128::new(count),
+                VoteCmp::Greater,
+                Uint128::new(count * 2),
+                threshold
+            ))
+        }
+
+        // Zero votes out of zero total power meet any threshold. When
+        // Geq is used. Always fail otherwise.
+        assert!(compare_vote_count(
+            Uint128::zero(),
+            VoteCmp::Geq,
+            Uint128::new(1),
+            Decimal::percent(0)
+        ));
+        assert!(compare_vote_count(
+            Uint128::zero(),
+            VoteCmp::Geq,
+            Uint128::new(0),
+            Decimal::percent(0)
+        ));
+        assert!(!compare_vote_count(
+            Uint128::zero(),
+            VoteCmp::Greater,
+            Uint128::new(1),
+            Decimal::percent(0)
+        ));
+        assert!(!compare_vote_count(
+            Uint128::zero(),
+            VoteCmp::Greater,
+            Uint128::new(0),
+            Decimal::percent(0)
+        ))
+    }
+}


Test patch:
diff --git a/contracts/cw-core/src/tests.rs b/contracts/cw-core/src/tests.rs
index 3a8fb5ce..c0dfe5d1 100644
--- a/contracts/cw-core/src/tests.rs
+++ b/contracts/cw-core/src/tests.rs
@@ -944,11 +944,9 @@ fn test_instantiate_with_items() {
     );
 
     assert_eq!(items[1], "item1".to_string());
-    let item1 = get_item(&mut app, gov_addr, "item1".to_string())
+    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 #"));
 }
 
 #[test]
diff --git a/contracts/cw-named-groups/src/tests.rs b/contracts/cw-named-groups/src/tests.rs
index 787c8033..9470c25d 100644
--- a/contracts/cw-named-groups/src/tests.rs
+++ b/contracts/cw-named-groups/src/tests.rs
@@ -17,15 +17,15 @@ fn named_group_contract() -> Box<dyn Contract<Empty>> {
     Box::new(contract)
 }
 
-const ADMIN: &str = "DAO";
-const USER1: &str = "USER1";
-const USER2: &str = "USER2";
-const USER3: &str = "USER3";
+const ADMIN: &str = "dao";
+const USER1: &str = "user1";
+const USER2: &str = "user2";
+const USER3: &str = "user3";
 
 fn group_factory(id: u8) -> Group {
     Group {
-        name: format!("GROUP{}_NAME", id),
-        addresses: vec![format!("USER{}", id)],
+        name: format!("group{}_name", id),
+        addresses: vec![format!("user{}", id)],
     }
 }
 
@@ -678,7 +678,7 @@ mod list_groups {
         let (app, contract_addr) = instantiate(None).unwrap();
 
         // List groups from a non-existent address.
-        let groups = list_groups(&app, &contract_addr, "ADDRESS".to_string(), None, None)
+        let groups = list_groups(&app, &contract_addr, "address".to_string(), None, None)
             .unwrap()
             .groups;
 
@@ -705,7 +705,7 @@ mod list_groups {
         .unwrap();
 
         // List groups from a non-existent address.
-        let groups = list_groups(&app, &contract_addr, "ADDRESS".to_string(), None, None)
+        let groups = list_groups(&app, &contract_addr, "address".to_string(), None, None)
             .unwrap()
             .groups;
 
@@ -842,8 +842,8 @@ mod is_address_in_group {
         let err = is_address_in_group(
             &app,
             &contract_addr,
-            "ADDRESS".to_string(),
-            "GROUP".to_string(),
+            "address".to_string(),
+            "group".to_string(),
         )
         .unwrap_err();
 
@@ -875,7 +875,7 @@ mod is_address_in_group {
 
         // Check if address is in group.
         let is_in_group =
-            is_address_in_group(&app, &contract_addr, "ADDRESS".to_string(), group1.name)
+            is_address_in_group(&app, &contract_addr, "address".to_string(), group1.name)
                 .unwrap()
                 .is_in_group;
 
diff --git a/contracts/cw-proposal-single/src/staking_tests.rs b/contracts/cw-proposal-single/src/staking_tests.rs
new file mode 100644
index 00000000..8d134728
--- /dev/null
+++ b/contracts/cw-proposal-single/src/staking_tests.rs
@@ -0,0 +1,146 @@
+use cosmwasm_std::{to_binary, Addr, Decimal, Empty, Uint128};
+use cw20::Cw20Coin;
+use cw_core::{msg::ModuleInstantiateInfo, query::DumpStateResponse};
+use cw_multi_test::{App, Contract, ContractWrapper, Executor};
+use cw_utils::Duration;
+
+use voting::Threshold;
+
+use crate::msg::InstantiateMsg;
+
+const WHALE_ADDR: &str = "whale";
+
+fn cw20_contract() -> Box<dyn Contract<Empty>> {
+    let contract = ContractWrapper::new(
+        cw20_base::contract::execute,
+        cw20_base::contract::instantiate,
+        cw20_base::contract::query,
+    );
+    Box::new(contract)
+}
+
+fn single_govmod_contract() -> Box<dyn Contract<Empty>> {
+    let contract = ContractWrapper::new(
+        crate::contract::execute,
+        crate::contract::instantiate,
+        crate::contract::query,
+    )
+    .with_reply(crate::contract::reply);
+    Box::new(contract)
+}
+
+fn staked_balances_voting() -> Box<dyn Contract<Empty>> {
+    let contract = ContractWrapper::new(
+        cw20_staked_balance_voting::contract::execute,
+        cw20_staked_balance_voting::contract::instantiate,
+        cw20_staked_balance_voting::contract::query,
+    )
+    .with_reply(cw20_staked_balance_voting::contract::reply);
+    Box::new(contract)
+}
+
+fn stake_cw20() -> Box<dyn Contract<Empty>> {
+    let contract = ContractWrapper::new(
+        stake_cw20::contract::execute,
+        stake_cw20::contract::instantiate,
+        stake_cw20::contract::query,
+    );
+    Box::new(contract)
+}
+
+fn core_contract() -> Box<dyn Contract<Empty>> {
+    let contract = ContractWrapper::new(
+        cw_core::contract::execute,
+        cw_core::contract::instantiate,
+        cw_core::contract::query,
+    )
+    .with_reply(cw_core::contract::reply);
+    Box::new(contract)
+}
+
+#[test]
+fn instantiate_with_staked_balances_voting() {
+    let mut app = App::default();
+
+    let govmod_id = app.store_code(single_govmod_contract());
+    let cw20_id = app.store_code(cw20_contract());
+    let stake_cw20_id = app.store_code(stake_cw20());
+    let core_contract_id = app.store_code(core_contract());
+    let staked_balances_voting_id = app.store_code(staked_balances_voting());
+
+    let instantiate_core = cw_core::msg::InstantiateMsg {
+        name: "DAO DAO".to_string(),
+        description: "A DAO that builds DAOs".to_string(),
+        image_url: None,
+        automatically_add_cw20s: true,
+        automatically_add_cw721s: false,
+        voting_module_instantiate_info: ModuleInstantiateInfo {
+            code_id: staked_balances_voting_id,
+            msg: to_binary(&cw20_staked_balance_voting::msg::InstantiateMsg {
+                active_threshold: None,
+                token_info: cw20_staked_balance_voting::msg::TokenInfo::New {
+                    code_id: cw20_id,
+                    label: "DAO DAO governance token.".to_string(),
+                    name: "DAO DAO".to_string(),
+                    symbol: "DAO".to_string(),
+                    decimals: 6,
+                    initial_balances: vec![Cw20Coin {
+                        address: WHALE_ADDR.to_string(),
+                        amount: Uint128::new(100),
+                    }],
+                    marketing: None,
+                    staking_code_id: stake_cw20_id,
+                    unstaking_duration: Some(Duration::Height(10u64)),
+                    initial_dao_balance: Some(Uint128::new(100)),
+                },
+            })
+            .unwrap(),
+            admin: cw_core::msg::Admin::None {},
+            label: "DAO DAO voting module".to_string(),
+        },
+        proposal_modules_instantiate_info: vec![ModuleInstantiateInfo {
+            code_id: govmod_id,
+            label: "DAO DAO governance module.".to_string(),
+            admin: cw_core::msg::Admin::CoreContract {},
+            msg: to_binary(&InstantiateMsg {
+                threshold: Threshold::ThresholdQuorum {
+                    threshold: voting::PercentageThreshold::Majority {},
+                    quorum: voting::PercentageThreshold::Percent(Decimal::percent(30)),
+                },
+                max_voting_period: Duration::Height(10u64),
+                only_members_execute: true,
+                deposit_info: None,
+            })
+            .unwrap(),
+        }],
+        initial_items: None,
+    };
+
+    let core_addr = app
+        .instantiate_contract(
+            core_contract_id,
+            Addr::unchecked(WHALE_ADDR),
+            &instantiate_core,
+            &[],
+            "DAO DAO",
+            None,
+        )
+        .unwrap();
+
+    let state: DumpStateResponse = app
+        .wrap()
+        .query_wasm_smart(core_addr, &cw_core::msg::QueryMsg::DumpState {})
+        .unwrap();
+
+    assert_eq!(state.governance_modules.len(), 1);
+    assert_eq!(
+        state.config,
+        cw_core::state::Config {
+            name: "DAO DAO".to_string(),
+            description: "A DAO that builds DAOs".to_string(),
+            image_url: None,
+            automatically_add_cw20s: true,
+            automatically_add_cw721s: false,
+        }
+    );
+}
diff --git a/contracts/cw-proposal-single/src/tests.rs b/contracts/cw-proposal-single/src/tests.rs
index f6a79fe6..2607e40a 100644
--- a/contracts/cw-proposal-single/src/tests.rs
+++ b/contracts/cw-proposal-single/src/tests.rs
@@ -2,17 +2,20 @@ use cosmwasm_std::{to_binary, Addr, Decimal, Empty, Uint128};
 use cw20::Cw20Coin;
 use cw20_staked_balance_voting::msg::ActiveThreshold;
 use cw_multi_test::{next_block, App, Contract, ContractWrapper, Executor};
+
+use cw_core::msg::ModuleInstantiateInfo;
+use cw_utils::Duration;
+
 use indexable_hooks::HooksResponse;
-use rand::{prelude::SliceRandom, Rng};
 
-use voting::{Vote, Votes};
+use testing::{ShouldExecute, TestVote};
+use voting::{PercentageThreshold, Status, Threshold, Vote, Votes};
 
 use crate::{
     msg::{DepositInfo, DepositToken, ExecuteMsg, InstantiateMsg, QueryMsg},
-    proposal::{Proposal, Status},
+    proposal::Proposal,
     query::{ProposalListResponse, ProposalResponse, VoteInfo, VoteResponse},
     state::{CheckedDepositInfo, Config},
-    threshold::{PercentageThreshold, Threshold},
 };
 
 const CREATOR_ADDR: &str = "creator";
@@ -75,27 +78,178 @@ fn cw_gov_contract() -> Box<dyn Contract<Empty>> {
     Box::new(contract)
 }
 
-fn instantiate_governance(app: &mut App, code_id: u64, msg: cw_core::msg::InstantiateMsg) -> Addr {
-    app.instantiate_contract(
-        code_id,
-        Addr::unchecked(CREATOR_ADDR),
-        &msg,
-        &[],
-        "cw-governance",
-        None,
+fn staked_balances_voting() -> Box<dyn Contract<Empty>> {
+    let contract = ContractWrapper::new(
+        cw20_staked_balance_voting::contract::execute,
+        cw20_staked_balance_voting::contract::instantiate,
+        cw20_staked_balance_voting::contract::query,
     )
-    .unwrap()
+    .with_reply(cw20_staked_balance_voting::contract::reply);
+    Box::new(contract)
+}
+
+fn stake_cw20() -> Box<dyn Contract<Empty>> {
+    let contract = ContractWrapper::new(
+        stake_cw20::contract::execute,
+        stake_cw20::contract::instantiate,
+        stake_cw20::contract::query,
+    );
+    Box::new(contract)
+}
+
+fn cw4_contract() -> Box<dyn Contract<Empty>> {
+    let contract = ContractWrapper::new(
+        cw4_group::contract::execute,
+        cw4_group::contract::instantiate,
+        cw4_group::contract::query,
+    );
+    Box::new(contract)
+}
+
+fn cw4_voting_contract() -> Box<dyn Contract<Empty>> {
+    let contract = ContractWrapper::new(
+        cw4_voting::contract::execute,
+        cw4_voting::contract::instantiate,
+        cw4_voting::contract::query,
+    )
+    .with_reply(cw4_voting::contract::reply);
+    Box::new(contract)
+}
+
+fn instantiate_with_staked_balances_governance(
+    app: &mut App,
+    governance_code_id: u64,
+    governance_instantiate: InstantiateMsg,
+    initial_balances: Option<Vec<Cw20Coin>>,
+) -> Addr {
+    let initial_balances = initial_balances.unwrap_or_else(|| {
+        vec![Cw20Coin {
+            address: CREATOR_ADDR.to_string(),
+            amount: Uint128::new(100_000_000),
+        }]
+    });
+
+    // Collapse balances so that we can test double votes.
+    let initial_balances: Vec<Cw20Coin> = {
+        let mut already_seen = vec![];
+        initial_balances
+            .into_iter()
+            .filter(|Cw20Coin { address, amount: _ }| {
+                if already_seen.contains(address) {
+                    false
+                } else {
+                    already_seen.push(address.clone());
+                    true
+                }
+            })
+            .collect()
+    };
+
+    let cw20_id = app.store_code(cw20_contract());
+    let stake_cw20_id = app.store_code(stake_cw20());
+    let staked_balances_voting_id = app.store_code(staked_balances_voting());
+    let core_contract_id = app.store_code(cw_gov_contract());
+
+    let instantiate_core = cw_core::msg::InstantiateMsg {
+        name: "DAO DAO".to_string(),
+        description: "A DAO that builds DAOs".to_string(),
+        image_url: None,
+        automatically_add_cw20s: true,
+        automatically_add_cw721s: false,
+        voting_module_instantiate_info: ModuleInstantiateInfo {
+            code_id: staked_balances_voting_id,
+            msg: to_binary(&cw20_staked_balance_voting::msg::InstantiateMsg {
+                active_threshold: None,
+                token_info: cw20_staked_balance_voting::msg::TokenInfo::New {
+                    code_id: cw20_id,
+                    label: "DAO DAO governance token.".to_string(),
+                    name: "DAO DAO".to_string(),
+                    symbol: "DAO".to_string(),
+                    decimals: 6,
+                    initial_balances: initial_balances.clone(),
+                    marketing: None,
+                    staking_code_id: stake_cw20_id,
+                    unstaking_duration: Some(Duration::Height(6)),
+                    initial_dao_balance: None,
+                },
+            })
+            .unwrap(),
+            admin: cw_core::msg::Admin::None {},
+            label: "DAO DAO voting module".to_string(),
+        },
+        proposal_modules_instantiate_info: vec![ModuleInstantiateInfo {
+            code_id: governance_code_id,
+            label: "DAO DAO governance module.".to_string(),
+            admin: cw_core::msg::Admin::CoreContract {},
+            msg: to_binary(&governance_instantiate).unwrap(),
+        }],
+        initial_items: None,
+    };
+
+    let core_addr = app
+        .instantiate_contract(
+            core_contract_id,
+            Addr::unchecked(CREATOR_ADDR),
+            &instantiate_core,
+            &[],
+            "DAO DAO",
+            None,
+        )
+        .unwrap();
+
+    let gov_state: cw_core::query::DumpStateResponse = app
+        .wrap()
+        .query_wasm_smart(core_addr.clone(), &cw_core::msg::QueryMsg::DumpState {})
+        .unwrap();
+    let voting_module = gov_state.voting_module;
+
+    let staking_contract: Addr = app
+        .wrap()
+        .query_wasm_smart(
+            voting_module.clone(),
+            &cw20_staked_balance_voting::msg::QueryMsg::StakingContract {},
+        )
+        .unwrap();
+    let token_contract: Addr = app
+        .wrap()
+        .query_wasm_smart(
+            voting_module,
+            &cw_core_interface::voting::Query::TokenContract {},
+        )
+        .unwrap();
+
+    // Stake all the initial balances.
+    for Cw20Coin { address, amount } in initial_balances {
+        app.execute_contract(
+            Addr::unchecked(&address),
+            token_contract.clone(),
+            &cw20::Cw20ExecuteMsg::Send {
+                contract: staking_contract.to_string(),
+                amount,
+                msg: to_binary(&stake_cw20::msg::ReceiveMsg::Stake {}).unwrap(),
+            },
+            &[],
+        )
+        .unwrap();
+    }
+
+    // Update the block so that those staked balances appear.
+    app.update_block(|block| block.height += 1);
+
+    core_addr
 }
 
-fn instantiate_with_default_governance(
+fn instantiate_with_staking_active_threshold(
     app: &mut App,
     code_id: u64,
     msg: InstantiateMsg,
     initial_balances: Option<Vec<Cw20Coin>>,
+    active_threshold: Option<ActiveThreshold>,
 ) -> Addr {
     let cw20_id = app.store_code(cw20_contract());
+    let cw20_staking_id = app.store_code(cw20_stake_contract());
     let governance_id = app.store_code(cw_gov_contract());
-    let votemod_id = app.store_code(cw20_balances_voting());
+    let votemod_id = app.store_code(cw20_staked_balances_voting());
 
     let initial_balances = initial_balances.unwrap_or_else(|| {
         vec![Cw20Coin {
@@ -112,8 +266,8 @@ fn instantiate_with_default_governance(
         automatically_add_cw721s: true,
         voting_module_instantiate_info: cw_core::msg::ModuleInstantiateInfo {
             code_id: votemod_id,
-            msg: to_binary(&cw20_balance_voting::msg::InstantiateMsg {
-                token_info: cw20_balance_voting::msg::TokenInfo::New {
+            msg: to_binary(&cw20_staked_balance_voting::msg::InstantiateMsg {
+                token_info: cw20_staked_balance_voting::msg::TokenInfo::New {
                     code_id: cw20_id,
                     label: "DAO DAO governance token".to_string(),
                     name: "DAO".to_string(),
@@ -121,7 +275,11 @@ fn instantiate_with_default_governance(
                     decimals: 6,
                     initial_balances,
                     marketing: None,
+                    staking_code_id: cw20_staking_id,
+                    unstaking_duration: None,
+                    initial_dao_balance: None,
                 },
+                active_threshold,
             })
             .unwrap(),
             admin: cw_core::msg::Admin::CoreContract {},
@@ -136,20 +294,100 @@ fn instantiate_with_default_governance(
         initial_items: None,
     };
 
-    instantiate_governance(app, governance_id, governance_instantiate)
+    app.instantiate_contract(
+        governance_id,
+        Addr::unchecked(CREATOR_ADDR),
+        &governance_instantiate,
+        &[],
+        "DAO DAO",
+        None,
+    )
+    .unwrap()
 }
 
-fn instantiate_with_staking_default_governance(
+fn instantiate_with_cw4_groups_governance(
     app: &mut App,
-    code_id: u64,
-    msg: InstantiateMsg,
+    governance_code_id: u64,
+    governance_instantiate: InstantiateMsg,
+    initial_weights: Option<Vec<Cw20Coin>>,
+) -> Addr {
+    let cw4_id = app.store_code(cw4_contract());
+    let core_id = app.store_code(cw_gov_contract());
+    let votemod_id = app.store_code(cw4_voting_contract());
+
+    let initial_weights = initial_weights.unwrap_or_default();
+
+    // Remove duplicates so that we can test duplicate voting.
+    let initial_weights: Vec<cw4::Member> = {
+        let mut already_seen = vec![];
+        initial_weights
+            .into_iter()
+            .filter(|Cw20Coin { address, .. }| {
+                if already_seen.contains(address) {
+                    false
+                } else {
+                    already_seen.push(address.clone());
+                    true
+                }
+            })
+            .map(|Cw20Coin { address, amount }| cw4::Member {
+                addr: address,
+                weight: amount.u128() as u64,
+            })
+            .collect()
+    };
+
+    let governance_instantiate = cw_core::msg::InstantiateMsg {
+        name: "DAO DAO".to_string(),
+        description: "A DAO that builds DAOs".to_string(),
+        image_url: None,
+        automatically_add_cw20s: true,
+        automatically_add_cw721s: true,
+        voting_module_instantiate_info: cw_core::msg::ModuleInstantiateInfo {
+            code_id: votemod_id,
+            msg: to_binary(&cw4_voting::msg::InstantiateMsg {
+                cw4_group_code_id: cw4_id,
+                initial_members: initial_weights,
+            })
+            .unwrap(),
+            admin: cw_core::msg::Admin::CoreContract {},
+            label: "DAO DAO voting module".to_string(),
+        },
+        proposal_modules_instantiate_info: vec![cw_core::msg::ModuleInstantiateInfo {
+            code_id: governance_code_id,
+            msg: to_binary(&governance_instantiate).unwrap(),
+            admin: cw_core::msg::Admin::CoreContract {},
+            label: "DAO DAO governance module".to_string(),
+        }],
+        initial_items: None,
+    };
+
+    let addr = app
+        .instantiate_contract(
+            core_id,
+            Addr::unchecked(CREATOR_ADDR),
+            &governance_instantiate,
+            &[],
+            "DAO DAO",
+            None,
+        )
+        .unwrap();
+
+    // Update the block so that weights appear.
+    app.update_block(|block| block.height += 1);
+
+    addr
+}
+
+fn instantiate_with_cw20_balances_governance(
+    app: &mut App,
+    governance_code_id: u64,
+    governance_instantiate: InstantiateMsg,
     initial_balances: Option<Vec<Cw20Coin>>,
-    active_threshold: Option<ActiveThreshold>,
 ) -> Addr {
     let cw20_id = app.store_code(cw20_contract());
-    let cw20_staking_id = app.store_code(cw20_stake_contract());
-    let governance_id = app.store_code(cw_gov_contract());
-    let votemod_id = app.store_code(cw20_staked_balances_voting());
+    let core_id = app.store_code(cw_gov_contract());
+    let votemod_id = app.store_code(cw20_balances_voting());
 
     let initial_balances = initial_balances.unwrap_or_else(|| {
         vec![Cw20Coin {
@@ -158,6 +396,22 @@ fn instantiate_with_staking_default_governance(
         }]
     });
 
+    // Collapse balances so that we can test double votes.
+    let initial_balances: Vec<Cw20Coin> = {
+        let mut already_seen = vec![];
+        initial_balances
+            .into_iter()
+            .filter(|Cw20Coin { address, amount: _ }| {
+                if already_seen.contains(address) {
+                    false
+                } else {
+                    already_seen.push(address.clone());
+                    true
+                }
+            })
+            .collect()
+    };
+
     let governance_instantiate = cw_core::msg::InstantiateMsg {
         name: "DAO DAO".to_string(),
         description: "A DAO that builds DAOs".to_string(),
@@ -166,8 +420,8 @@ fn instantiate_with_staking_default_governance(
         automatically_add_cw721s: true,
         voting_module_instantiate_info: cw_core::msg::ModuleInstantiateInfo {
             code_id: votemod_id,
-            msg: to_binary(&cw20_staked_balance_voting::msg::InstantiateMsg {
-                token_info: cw20_staked_balance_voting::msg::TokenInfo::New {
+            msg: to_binary(&cw20_balance_voting::msg::InstantiateMsg {
+                token_info: cw20_balance_voting::msg::TokenInfo::New {
                     code_id: cw20_id,
                     label: "DAO DAO governance token".to_string(),
                     name: "DAO".to_string(),
@@ -175,46 +429,126 @@ fn instantiate_with_staking_default_governance(
                     decimals: 6,
                     initial_balances,
                     marketing: None,
-                    staking_code_id: cw20_staking_id,
-                    unstaking_duration: None,
-                    initial_dao_balance: None,
                 },
-                active_threshold,
             })
             .unwrap(),
             admin: cw_core::msg::Admin::CoreContract {},
             label: "DAO DAO voting module".to_string(),
         },
         proposal_modules_instantiate_info: vec![cw_core::msg::ModuleInstantiateInfo {
-            code_id,
-            msg: to_binary(&msg).unwrap(),
+            code_id: governance_code_id,
+            msg: to_binary(&governance_instantiate).unwrap(),
             admin: cw_core::msg::Admin::CoreContract {},
             label: "DAO DAO governance module".to_string(),
         }],
         initial_items: None,
     };
 
-    instantiate_governance(app, governance_id, governance_instantiate)
+    app.instantiate_contract(
+        core_id,
+        Addr::unchecked(CREATOR_ADDR),
+        &governance_instantiate,
+        &[],
+        "DAO DAO",
+        None,
+    )
+    .unwrap()
 }
 
-#[test]
-fn test_propose() {
+fn do_votes_cw20_balances(
+    votes: Vec<TestVote>,
+    threshold: Threshold,
+    expected_status: Status,
+    total_supply: Option<Uint128>,
+) {
+    do_test_votes(
+        votes,
+        threshold,
+        expected_status,
+        total_supply,
+        None,
+        instantiate_with_cw20_balances_governance,
+    );
+}
+
+fn do_votes_staked_balances(
+    votes: Vec<TestVote>,
+    threshold: Threshold,
+    expected_status: Status,
+    total_supply: Option<Uint128>,
+) {
+    do_test_votes(
+        votes,
+        threshold,
+        expected_status,
+        total_supply,
+        None,
+        instantiate_with_staked_balances_governance,
+    );
+}
+
+fn do_votes_cw4_weights(
+    votes: Vec<TestVote>,
+    threshold: Threshold,
+    expected_status: Status,
+    total_supply: Option<Uint128>,
+) {
+    do_test_votes(
+        votes,
+        threshold,
+        expected_status,
+        total_supply,
+        None,
+        instantiate_with_cw4_groups_governance,
+    );
+}
+
+fn do_test_votes<F>(
+    votes: Vec<TestVote>,
+    threshold: Threshold,
+    expected_status: Status,
+    total_supply: Option<Uint128>,
+    deposit_info: Option<DepositInfo>,
+    setup_governance: F,
+) -> (App, Addr)
+where
+    F: Fn(&mut App, u64, InstantiateMsg, Option<Vec<Cw20Coin>>) -> Addr,
+{
     let mut app = App::default();
     let govmod_id = app.store_code(single_govmod_contract());
 
-    let threshold = Threshold::AbsolutePercentage {
-        percentage: PercentageThreshold::Majority {},
+    let mut initial_balances = votes
+        .iter()
+        .map(|TestVote { voter, weight, .. }| Cw20Coin {
+            address: voter.to_string(),
+            amount: *weight,
+        })
+        .collect::<Vec<Cw20Coin>>();
+    let initial_balances_supply = votes.iter().fold(Uint128::zero(), |p, n| p + n.weight);
+    let to_fill = total_supply.map(|total_supply| total_supply - initial_balances_supply);
+    if let Some(fill) = to_fill {
+        initial_balances.push(Cw20Coin {
+            address: "filler".to_string(),
+            amount: fill,
+        })
+    }
+
+    let proposer = match votes.first() {
+        Some(vote) => vote.voter.clone(),
+        None => panic!("do_test_votes must have at least one vote."),
     };
+
     let max_voting_period = cw_utils::Duration::Height(6);
     let instantiate = InstantiateMsg {
-        threshold: threshold.clone(),
+        threshold,
         max_voting_period,
         only_members_execute: false,
-        deposit_info: None,
+        deposit_info,
     };
 
     let governance_addr =
-        instantiate_with_default_governance(&mut app, govmod_id, instantiate, None);
+        setup_governance(&mut app, govmod_id, instantiate, Some(initial_balances));
+
     let governance_modules: Vec<Addr> = app
         .wrap()
         .query_wasm_smart(
@@ -229,144 +563,7 @@ fn test_propose() {
     assert_eq!(governance_modules.len(), 1);
     let govmod_single = governance_modules.into_iter().next().unwrap();
 
-    // Check that the governance module has been configured correctly.
-    let config: Config = app
-        .wrap()
-        .query_wasm_smart(govmod_single.clone(), &QueryMsg::Config {})
-        .unwrap();
-    let expected = Config {
-        threshold: threshold.clone(),
-        max_voting_period,
-        only_members_execute: false,
-        dao: governance_addr,
-        deposit_info: None,
-    };
-    assert_eq!(config, expected);
-
-    // Create a new proposal.
-    app.execute_contract(
-        Addr::unchecked(CREATOR_ADDR),
-        govmod_single.clone(),
-        &ExecuteMsg::Propose {
-            title: "A simple text proposal".to_string(),
-            description: "This is a simple text proposal".to_string(),
-            msgs: vec![],
-        },
-        &[],
-    )
-    .unwrap();
-
-    let created: ProposalResponse = app
-        .wrap()
-        .query_wasm_smart(govmod_single, &QueryMsg::Proposal { proposal_id: 1 })
-        .unwrap();
-    let current_block = app.block_info();
-    let expected = Proposal {
-        title: "A simple text proposal".to_string(),
-        description: "This is a simple text proposal".to_string(),
-        proposer: Addr::unchecked(CREATOR_ADDR),
-        start_height: current_block.height,
-        expiration: max_voting_period.after(&current_block),
-        threshold,
-        total_power: Uint128::new(100_000_000),
-        msgs: vec![],
-        status: crate::proposal::Status::Open,
-        votes: Votes::zero(),
-        deposit_info: None,
-    };
-
-    assert_eq!(created.proposal, expected);
-    assert_eq!(created.id, 1u64);
-}
-
-/// If a test vote should execute. Used for fuzzing and checking that
-/// votes after a proposal has completed aren't allowed.
-pub enum ShouldExecute {
-    /// This should execute.
-    Yes,
-    /// This should not execute.
-    No,
-    /// Doesn't matter.
-    Meh,
-}
-
-struct TestVote {
-    /// The address casting the vote.
-    voter: String,
-    /// Position on the vote.
-    position: Vote,
-    /// Voting power of the address.
-    weight: Uint128,
-    /// If this vote is expected to execute.
-    should_execute: ShouldExecute,
-}
-
-// Creates a proposal and then executes a series of votes on those
-// proposals. Asserts both that those votes execute as expected and
-// that the final status of the proposal is what is expected. Returns
-// the address of the governance contract that it has created so that
-// callers may do additional inspection of the contract's state.
-fn do_test_votes(
-    votes: Vec<TestVote>,
-    threshold: Threshold,
-    expected_status: Status,
-    total_supply: Option<Uint128>,
-    deposit_info: Option<DepositInfo>,
-) -> (App, Addr) {
-    let mut app = App::default();
-    let govmod_id = app.store_code(single_govmod_contract());
-
-    let mut initial_balances = votes
-        .iter()
-        .map(|TestVote { voter, weight, .. }| Cw20Coin {
-            address: voter.to_string(),
-            amount: *weight,
-        })
-        .collect::<Vec<Cw20Coin>>();
-    let initial_balances_supply = votes.iter().fold(Uint128::zero(), |p, n| p + n.weight);
-    let to_fill = total_supply.map(|total_supply| total_supply - initial_balances_supply);
-    if let Some(fill) = to_fill {
-        initial_balances.push(Cw20Coin {
-            address: "filler".to_string(),
-            amount: fill,
-        })
-    }
-
-    let proposer = match votes.first() {
-        Some(vote) => vote.voter.clone(),
-        None => panic!("do_test_votes must have at least one vote."),
-    };
-
-    let max_voting_period = cw_utils::Duration::Height(6);
-    let instantiate = InstantiateMsg {
-        threshold,
-        max_voting_period,
-        only_members_execute: false,
-        deposit_info,
-    };
-
-    let governance_addr = instantiate_with_default_governance(
-        &mut app,
-        govmod_id,
-        instantiate,
-        Some(initial_balances),
-    );
-
-    let governance_modules: Vec<Addr> = app
-        .wrap()
-        .query_wasm_smart(
-            governance_addr.clone(),
-            &cw_core::msg::QueryMsg::ProposalModules {
-                start_at: None,
-                limit: None,
-            },
-        )
-        .unwrap();
-
-    assert_eq!(governance_modules.len(), 1);
-    let govmod_single = governance_modules.into_iter().next().unwrap();
-
-    // Allow a proposal deposit as needed.
+    // Allow a proposal deposit as needed.
     let config: Config = app
         .wrap()
         .query_wasm_smart(govmod_single.clone(), &QueryMsg::Config {})
@@ -465,489 +662,198 @@ fn do_test_votes(
     (app, governance_addr)
 }
 
-#[test]
-fn test_vote_simple() {
+// Creates a proposal and then executes a series of votes on those
+// proposals. Asserts both that those votes execute as expected and
+// that the final status of the proposal is what is expected. Returns
+// the address of the governance contract that it has created so that
+// callers may do additional inspection of the contract's state.
+fn do_test_votes_cw20_balances(
+    votes: Vec<TestVote>,
+    threshold: Threshold,
+    expected_status: Status,
+    total_supply: Option<Uint128>,
+    deposit_info: Option<DepositInfo>,
+) -> (App, Addr) {
     do_test_votes(
-        vec![TestVote {
-            voter: "ekez".to_string(),
-            position: Vote::Yes,
-            weight: Uint128::new(10),
-            should_execute: ShouldExecute::Yes,
-        }],
-        Threshold::AbsolutePercentage {
-            percentage: PercentageThreshold::Percent(Decimal::percent(100)),
-        },
-        Status::Passed,
-        None,
-        None,
-    );
+        votes,
+        threshold,
+        expected_status,
+        total_supply,
+        deposit_info,
+        instantiate_with_cw20_balances_governance,
+    )
+}
 
-    do_test_votes(
-        vec![TestVote {
-            voter: "ekez".to_string(),
-            position: Vote::No,
-            weight: Uint128::new(10),
-            should_execute: ShouldExecute::Yes,
-        }],
-        Threshold::AbsolutePercentage {
-            percentage: PercentageThreshold::Percent(Decimal::percent(100)),
+#[test]
+fn test_propose() {
+    let mut app = App::default();
+    let govmod_id = app.store_code(single_govmod_contract());
+
+    let threshold = Threshold::AbsolutePercentage {
+        percentage: PercentageThreshold::Majority {},
+    };
+    let max_voting_period = cw_utils::Duration::Height(6);
+    let instantiate = InstantiateMsg {
+        threshold: threshold.clone(),
+        max_voting_period,
+        only_members_execute: false,
+        deposit_info: None,
+    };
+
+    let governance_addr =
+        instantiate_with_cw20_balances_governance(&mut app, govmod_id, instantiate, None);
+    let governance_modules: Vec<Addr> = app
+        .wrap()
+        .query_wasm_smart(
+            governance_addr.clone(),
+            &cw_core::msg::QueryMsg::ProposalModules {
+                start_at: None,
+                limit: None,
+            },
+        )
+        .unwrap();
+
+    assert_eq!(governance_modules.len(), 1);
+    let govmod_single = governance_modules.into_iter().next().unwrap();
+
+    // Check that the governance module has been configured correctly.
+    let config: Config = app
+        .wrap()
+        .query_wasm_smart(govmod_single.clone(), &QueryMsg::Config {})
+        .unwrap();
+    let expected = Config {
+        threshold: threshold.clone(),
+        max_voting_period,
+        only_members_execute: false,
+        dao: governance_addr,
+        deposit_info: None,
+    };
+    assert_eq!(config, expected);
+
+    // Create a new proposal.
+    app.execute_contract(
+        Addr::unchecked(CREATOR_ADDR),
+        govmod_single.clone(),
+        &ExecuteMsg::Propose {
+            title: "A simple text proposal".to_string(),
+            description: "This is a simple text proposal".to_string(),
+            msgs: vec![],
         },
-        Status::Rejected,
-        None,
-        None,
-    );
+        &[],
+    )
+    .unwrap();
+
+    let created: ProposalResponse = app
+        .wrap()
+        .query_wasm_smart(govmod_single, &QueryMsg::Proposal { proposal_id: 1 })
+        .unwrap();
+    let current_block = app.block_info();
+    let expected = Proposal {
+        title: "A simple text proposal".to_string(),
+        description: "This is a simple text proposal".to_string(),
+        proposer: Addr::unchecked(CREATOR_ADDR),
+        start_height: current_block.height,
+        expiration: max_voting_period.after(&current_block),
+        threshold,
+        total_power: Uint128::new(100_000_000),
+        msgs: vec![],
+        status: Status::Open,
+        votes: Votes::zero(),
+        deposit_info: None,
+    };
+
+    assert_eq!(created.proposal, expected);
+    assert_eq!(created.id, 1u64);
 }
 
 #[test]
-fn test_vote_no_overflow() {
-    // We should not overflow when computing passing thresholds even
-    // when there are 2^128 votes.
-    do_test_votes(
-        vec![TestVote {
-            voter: "ekez".to_string(),
-            position: Vote::Yes,
-            weight: Uint128::new(u128::max_value()),
-            should_execute: ShouldExecute::Yes,
-        }],
-        Threshold::AbsolutePercentage {
-            percentage: PercentageThreshold::Percent(Decimal::percent(100)),
-        },
-        Status::Passed,
-        None,
-        None,
-    );
+fn test_vote_simple() {
+    testing::test_simple_votes(do_votes_cw20_balances);
+    testing::test_simple_votes(do_votes_cw4_weights);
+    testing::test_simple_votes(do_votes_staked_balances)
 }
 
 #[test]
-fn test_vote_abstain_only() {
-    do_test_votes(
-        vec![TestVote {
-            voter: "ekez".to_string(),
-            position: Vote::Abstain,
-            weight: Uint128::new(u128::max_value()),
-            should_execute: ShouldExecute::Yes,
-        }],
-        Threshold::AbsolutePercentage {
-            percentage: PercentageThreshold::Percent(Decimal::percent(100)),
-        },
-        Status::Rejected,
-        None,
-        None,
-    );
+fn test_simple_vote_no_overflow() {
+    testing::test_simple_vote_no_overflow(do_votes_cw20_balances);
+    testing::test_simple_vote_no_overflow(do_votes_staked_balances)
+}
 
-    // The quorum shouldn't matter here in determining if the vote is
-    // rejected.
-    for i in 0..101 {
-        do_test_votes(
-            vec![TestVote {
-                voter: "ekez".to_string(),
-                position: Vote::Abstain,
-                weight: Uint128::new(u128::max_value()),
-                should_execute: ShouldExecute::Yes,
-            }],
-            Threshold::ThresholdQuorum {
-                threshold: PercentageThreshold::Percent(Decimal::percent(100)),
-                quorum: PercentageThreshold::Percent(Decimal::percent(i)),
-            },
-            Status::Rejected,
-            None,
-            None,
-        );
-    }
+#[test]
+fn test_vote_no_overflow() {
+    testing::test_vote_no_overflow(do_votes_cw20_balances);
+    testing::test_vote_no_overflow(do_votes_staked_balances)
 }
 
 #[test]
-fn test_single_no() {
-    do_test_votes(
-        vec![TestVote {
-            voter: "ekez".to_string(),
-            position: Vote::No,
-            weight: Uint128::new(1),
-            should_execute: ShouldExecute::Yes,
-        }],
-        Threshold::AbsolutePercentage {
-            percentage: PercentageThreshold::Percent(Decimal::percent(100)),
-        },
-        Status::Rejected,
-        Some(Uint128::from(u128::max_value())),
-        None,
-    );
+fn test_simple_early_rejection() {
+    testing::test_simple_early_rejection(do_votes_cw20_balances);
+    testing::test_simple_early_rejection(do_votes_cw4_weights);
+    testing::test_simple_early_rejection(do_votes_staked_balances)
 }
 
 #[test]
-fn test_tricky_rounding() {
-    // This tests the smallest possible round up for passing
-    // thresholds we can have. Specifically, a 1% passing threshold
-    // and 1 total vote. This should round up and only pass if there
-    // are more than 1 yes votes.
-    do_test_votes(
-        vec![TestVote {
-            voter: "ekez".to_string(),
-            position: Vote::Yes,
-            weight: Uint128::new(1),
-            should_execute: ShouldExecute::Yes,
-        }],
-        Threshold::AbsolutePercentage {
-            percentage: PercentageThreshold::Percent(Decimal::percent(1)),
-        },
-        Status::Passed,
-        None,
-        None,
-    );
+fn test_vote_abstain_only() {
+    testing::test_vote_abstain_only(do_votes_cw20_balances);
+    testing::test_vote_abstain_only(do_votes_cw4_weights);
+    testing::test_vote_abstain_only(do_votes_staked_balances)
+}
 
-    do_test_votes(
-        vec![TestVote {
-            voter: "ekez".to_string(),
-            position: Vote::Abstain,
-            weight: Uint128::new(1),
-            should_execute: ShouldExecute::Yes,
-        }],
-        Threshold::AbsolutePercentage {
-            percentage: PercentageThreshold::Percent(Decimal::percent(1)),
-        },
-        Status::Rejected,
-        None,
-        None,
-    );
+#[test]
+fn test_tricky_rounding() {
+    testing::test_tricky_rounding(do_votes_cw20_balances);
+    testing::test_tricky_rounding(do_votes_cw4_weights);
+    testing::test_tricky_rounding(do_votes_staked_balances)
 }
 
 #[test]
 fn test_no_double_votes() {
-    do_test_votes(
-        vec![
-            TestVote {
-                voter: "ekez".to_string(),
-                position: Vote::Abstain,
-                weight: Uint128::new(2),
-                should_execute: ShouldExecute::Yes,
-            },
-            TestVote {
-                voter: "ekez".to_string(),
-                position: Vote::Yes,
-                weight: Uint128::new(2),
-                should_execute: ShouldExecute::No,
-            },
-        ],
-        Threshold::AbsolutePercentage {
-            percentage: PercentageThreshold::Percent(Decimal::percent(100)),
-        },
-        // NOTE: Updating our cw20-base version will cause this to
-        // fail. In versions of cw20-base before Feb 15 2022 (the one
-        // we use at the time of writing) it was allowed to have an
-        // initial balance that repeats for a given address but it
-        // would cause miscalculation of the total supply. In this
-        // case the total supply is miscumputed to be 4 so this is
-        // assumed to have 2 abstain votes out of 4 possible votes.
-        Status::Open,
-        None,
-        None,
-    );
+    testing::test_no_double_votes(do_votes_cw20_balances);
+    testing::test_no_double_votes(do_votes_cw4_weights);
+    testing::test_no_double_votes(do_votes_staked_balances);
 }
 
-/// The current behavior for passing proposals is that the first
-/// option to reach the threshold wins. For example, with a 50%
-/// passing threshold if 50% of voting power votes no that proposal
-/// fails even if the other 50% would have voted yes. The same goes if
-/// the yes and no were reversed.
-///
-/// TODO(zeke): is this the behavior that we want?
 #[test]
-fn test_close_votes() {
-    do_test_votes(
-        vec![
-            TestVote {
-                voter: "ekez".to_string(),
-                position: Vote::Abstain,
-                weight: Uint128::new(10),
-                should_execute: ShouldExecute::Yes,
-            },
-            TestVote {
-                voter: "keze".to_string(),
-                position: Vote::No,
-                weight: Uint128::new(5),
-                should_execute: ShouldExecute::Yes,
-            },
-            TestVote {
-                voter: "ezek".to_string(),
-                position: Vote::Yes,
-                weight: Uint128::new(5),
-                should_execute: ShouldExecute::Yes,
-            },
-        ],
-        Threshold::AbsolutePercentage {
-            percentage: PercentageThreshold::Percent(Decimal::percent(50)),
-        },
-        Status::Passed,
-        None,
-        None,
-    );
-
-    do_test_votes(
-        vec![
-            TestVote {
-                voter: "ekez".to_string(),
-                position: Vote::Abstain,
-                weight: Uint128::new(10),
-                should_execute: ShouldExecute::Yes,
-            },
-            TestVote {
-                voter: "keze".to_string(),
-                position: Vote::Yes,
-                weight: Uint128::new(5),
-                should_execute: ShouldExecute::Yes,
-            },
-            TestVote {
-                voter: "ezek".to_string(),
-                position: Vote::No,
-                weight: Uint128::new(5),
-                should_execute: ShouldExecute::No,
-            },
-        ],
-        Threshold::AbsolutePercentage {
-            percentage: PercentageThreshold::Percent(Decimal::percent(50)),
-        },
-        Status::Passed,
-        None,
-        None,
-    );
+fn test_votes_favor_yes() {
+    testing::test_votes_favor_yes(do_votes_cw20_balances);
+    testing::test_votes_favor_yes(do_votes_staked_balances);
 }
 
-/// Another test which demonstrates the trouble with our "first to
-/// reach threshold" method for determining the winner. In this case
-/// there are more no votes than yes votes but because yes votes are
-/// the first ones to reach the threshold after the quorum has been
-/// passed yes votes win.
-///
-/// This is a pretty nonsense passing threshold but helps demonstrate
-/// the issue well enough.
 #[test]
-fn test_close_votes_quorum() {
-    do_test_votes(
-        vec![
-            TestVote {
-                voter: "ekez".to_string(),
-                position: Vote::No,
-                weight: Uint128::new(10),
-                should_execute: ShouldExecute::Yes,
-            },
-            TestVote {
-                voter: "keze".to_string(),
-                position: Vote::Yes,
-                weight: Uint128::new(5),
-                should_execute: ShouldExecute::Yes,
-            },
-            TestVote {
-                voter: "ezek".to_string(),
-                position: Vote::No,
-                weight: Uint128::new(10),
-                should_execute: ShouldExecute::No,
-            },
-        ],
-        Threshold::ThresholdQuorum {
-            threshold: PercentageThreshold::Percent(Decimal::percent(10)),
-            quorum: PercentageThreshold::Majority {},
-        },
-        Status::Passed,
-        None,
-        None,
-    );
+fn test_votes_low_threshold() {
+    testing::test_votes_low_threshold(do_votes_cw20_balances);
+    testing::test_votes_low_threshold(do_votes_cw4_weights);
+    testing::test_votes_low_threshold(do_votes_staked_balances)
 }
 
 #[test]
 fn test_majority_vs_half() {
-    do_test_votes(
-        vec![
-            TestVote {
-                voter: "ekez".to_string(),
-                position: Vote::No,
-                weight: Uint128::new(10),
-                should_execute: ShouldExecute::Yes,
-            },
-            TestVote {
-                voter: "keze".to_string(),
-                position: Vote::Yes,
-                weight: Uint128::new(10),
-                should_execute: ShouldExecute::Yes,
-            },
-        ],
-        Threshold::ThresholdQuorum {
-            threshold: PercentageThreshold::Percent(Decimal::percent(50)),
-            quorum: PercentageThreshold::Majority {},
-        },
-        Status::Passed,
-        None,
-        None,
-    );
-
-    do_test_votes(
-        vec![
-            TestVote {
-                voter: "ekez".to_string(),
-                position: Vote::No,
-                weight: Uint128::new(10),
-                should_execute: ShouldExecute::Yes,
-            },
-            TestVote {
-                voter: "keze".to_string(),
-                position: Vote::Yes,
-                weight: Uint128::new(10),
-                should_execute: ShouldExecute::No,
-            },
-        ],
-        Threshold::ThresholdQuorum {
-            threshold: PercentageThreshold::Majority {},
-            quorum: PercentageThreshold::Majority {},
-        },
-        Status::Rejected,
-        None,
-        None,
-    );
+    testing::test_majority_vs_half(do_votes_cw20_balances);
+    testing::test_majority_vs_half(do_votes_cw4_weights);
+    testing::test_majority_vs_half(do_votes_staked_balances)
 }
 
 #[test]
 fn test_pass_threshold_not_quorum() {
-    do_test_votes(
-        vec![TestVote {
-            voter: "ekez".to_string(),
-            position: Vote::Yes,
-            weight: Uint128::new(59),
-            should_execute: ShouldExecute::Yes,
-        }],
-        Threshold::ThresholdQuorum {
-            threshold: PercentageThreshold::Majority {},
-            quorum: PercentageThreshold::Percent(Decimal::percent(60)),
-        },
-        Status::Open,
-        Some(Uint128::new(100)),
-        None,
-    );
-    do_test_votes(
-        vec![TestVote {
-            voter: "ekez".to_string(),
-            position: Vote::No,
-            weight: Uint128::new(59),
-            should_execute: ShouldExecute::Yes,
-        }],
-        Threshold::ThresholdQuorum {
-            threshold: PercentageThreshold::Majority {},
-            quorum: PercentageThreshold::Percent(Decimal::percent(60)),
-        },
-        // As the threshold is 50% and 59% of voters have voted no
-        // this is unable to pass.
-        Status::Rejected,
-        Some(Uint128::new(100)),
-        None,
-    );
+    testing::test_pass_threshold_not_quorum(do_votes_cw20_balances);
+    testing::test_pass_threshold_not_quorum(do_votes_cw4_weights);
+    testing::test_pass_threshold_not_quorum(do_votes_staked_balances)
 }
 
 #[test]
 fn test_pass_threshold_exactly_quorum() {
-    do_test_votes(
-        vec![TestVote {
-            voter: "ekez".to_string(),
-            position: Vote::Yes,
-            weight: Uint128::new(60),
-            should_execute: ShouldExecute::Yes,
-        }],
-        Threshold::ThresholdQuorum {
-            threshold: PercentageThreshold::Majority {},
-            quorum: PercentageThreshold::Percent(Decimal::percent(60)),
-        },
-        Status::Passed,
-        Some(Uint128::new(100)),
-        None,
-    );
-    do_test_votes(
-        vec![
-            TestVote {
-                voter: "ekez".to_string(),
-                position: Vote::Yes,
-                weight: Uint128::new(59),
-                should_execute: ShouldExecute::Yes,
-            },
-            // This is an intersting one because in this case the no
-            // voter is actually incentivised not to vote. By voting
-            // they move the quorum over the threshold and pass the
-            // vote. In a DAO with sufficently involved stakeholders
-            // no voters should effectively never vote if there is a
-            // quorum higher than the threshold as it makes the
-            // passing threshold the quorum threshold.
-            TestVote {
-                voter: "keze".to_string(),
-                position: Vote::No,
-                weight: Uint128::new(1),
-                should_execute: ShouldExecute::Yes,
-            },
-        ],
-        Threshold::ThresholdQuorum {
-            threshold: PercentageThreshold::Majority {},
-            quorum: PercentageThreshold::Percent(Decimal::percent(60)),
-        },
-        Status::Passed,
-        Some(Uint128::new(100)),
-        None,
-    );
-    do_test_votes(
-        vec![TestVote {
-            voter: "ekez".to_string(),
-            position: Vote::No,
-            weight: Uint128::new(60),
-            should_execute: ShouldExecute::Yes,
-        }],
-        Threshold::ThresholdQuorum {
-            threshold: PercentageThreshold::Majority {},
-            quorum: PercentageThreshold::Percent(Decimal::percent(60)),
-        },
-        Status::Rejected,
-        Some(Uint128::new(100)),
-        None,
-    );
+    testing::test_pass_exactly_quorum(do_votes_cw20_balances);
+    testing::test_pass_exactly_quorum(do_votes_cw4_weights);
+    testing::test_pass_exactly_quorum(do_votes_staked_balances);
 }
 
 /// Generate some random voting selections and make sure they behave
 /// as expected.
 #[test]
 fn fuzz_voting() {
-    let mut rng = rand::thread_rng();
-    let dist = rand::distributions::Uniform::<u64>::new(1, 200);
-    for _ in 0..25 {
-        let yes: Vec<u64> = (0..50).map(|_| rng.sample(&dist)).collect();
-        let no: Vec<u64> = (0..50).map(|_| rng.sample(&dist)).collect();
-
-        let yes_sum: u64 = yes.iter().sum();
-        let no_sum: u64 = no.iter().sum();
-        let expected_status = match yes_sum.cmp(&no_sum) {
-            std::cmp::Ordering::Less => Status::Rejected,
-            // Depends on which reaches the threshold first. Ignore for now.
-            std::cmp::Ordering::Equal => Status::Rejected,
-            std::cmp::Ordering::Greater => Status::Passed,
-        };
-
-        let yes = yes.into_iter().enumerate().map(|(idx, weight)| TestVote {
-            voter: format!("yes_{}", idx),
-            position: Vote::Yes,
-            weight: Uint128::new(weight as u128),
-            should_execute: ShouldExecute::Meh,
-        });
-        let no = no.into_iter().enumerate().map(|(idx, weight)| TestVote {
-            voter: format!("no_{}", idx),
-            position: Vote::No,
-            weight: Uint128::new(weight as u128),
-            should_execute: ShouldExecute::Meh,
-        });
-        let mut votes = yes.chain(no).collect::<Vec<_>>();
-        votes.shuffle(&mut rng);
-
-        do_test_votes(
-            votes,
-            Threshold::AbsolutePercentage {
-                percentage: PercentageThreshold::Majority {},
-            },
-            expected_status,
-            None,
-            None,
-        );
-    }
+    testing::fuzz_voting(do_votes_cw20_balances);
+    testing::fuzz_voting(do_votes_cw4_weights);
+    testing::fuzz_voting(do_votes_staked_balances);
 }
 
 /// Instantiate the contract and use the voting module's token
@@ -973,7 +879,7 @@ fn test_voting_module_token_proposal_deposit_instantiate() {
     };
 
     let governance_addr =
-        instantiate_with_default_governance(&mut app, govmod_id, instantiate, None);
+        instantiate_with_cw20_balances_governance(&mut app, govmod_id, instantiate, None);
 
     let gov_state: cw_core::query::DumpStateResponse = app
         .wrap()
@@ -1049,7 +955,7 @@ fn test_different_token_proposal_deposit() {
         }),
     };
 
-    instantiate_with_default_governance(&mut app, govmod_id, instantiate, None);
+    instantiate_with_cw20_balances_governance(&mut app, govmod_id, instantiate, None);
 }
 
 /// Try to instantiate the governance module with a non-cw20 as its
@@ -1104,7 +1010,7 @@ fn test_bad_token_proposal_deposit() {
         }),
     };
 
-    instantiate_with_default_governance(&mut app, govmod_id, instantiate, None);
+    instantiate_with_cw20_balances_governance(&mut app, govmod_id, instantiate, None);
 }
 
 #[test]
@@ -1127,7 +1033,7 @@ fn test_take_proposal_deposit() {
         }),
     };
 
-    let governance_addr = instantiate_with_default_governance(
+    let governance_addr = instantiate_with_cw20_balances_governance(
         &mut app,
         govmod_id,
         instantiate,
@@ -1216,7 +1122,7 @@ fn test_deposit_return_on_execute() {
     // Will create a proposal and execute it, one token will be
     // deposited to create said proposal, expectation is that the
     // token is then returned once the proposal is executed.
-    let (mut app, governance_addr) = do_test_votes(
+    let (mut app, governance_addr) = do_test_votes_cw20_balances(
         vec![TestVote {
             voter: "ekez".to_string(),
             position: Vote::Yes,
@@ -1288,7 +1194,7 @@ fn test_deposit_return_on_execute() {
 
 #[test]
 fn test_close_open_proposal() {
-    let (mut app, governance_addr) = do_test_votes(
+    let (mut app, governance_addr) = do_test_votes_cw20_balances(
         vec![TestVote {
             voter: "ekez".to_string(),
             position: Vote::No,
@@ -1362,7 +1268,7 @@ fn test_close_open_proposal() {
 
 #[test]
 fn test_deposit_return_on_close() {
-    let (mut app, governance_addr) = do_test_votes(
+    let (mut app, governance_addr) = do_test_votes_cw20_balances(
         vec![TestVote {
             voter: "ekez".to_string(),
             position: Vote::No,
@@ -1434,7 +1340,7 @@ fn test_deposit_return_on_close() {
 
 #[test]
 fn test_update_config() {
-    let (mut app, governance_addr) = do_test_votes(
+    let (mut app, governance_addr) = do_test_votes_cw20_balances(
         vec![TestVote {
             voter: "ekez".to_string(),
             position: Vote::No,
@@ -1548,7 +1454,7 @@ fn test_update_config() {
 
 #[test]
 fn test_no_return_if_no_refunds() {
-    let (mut app, governance_addr) = do_test_votes(
+    let (mut app, governance_addr) = do_test_votes_cw20_balances(
         vec![TestVote {
             voter: "ekez".to_string(),
             position: Vote::No,
@@ -1609,7 +1515,7 @@ fn test_no_return_if_no_refunds() {
 fn test_query_list_proposals() {
     let mut app = App::default();
     let govmod_id = app.store_code(single_govmod_contract());
-    let gov_addr = instantiate_with_default_governance(
+    let gov_addr = instantiate_with_cw20_balances_governance(
         &mut app,
         govmod_id,
         InstantiateMsg {
@@ -1766,7 +1672,7 @@ fn test_hooks() {
     };
 
     let governance_addr =
-        instantiate_with_default_governance(&mut app, govmod_id, instantiate, None);
+        instantiate_with_cw20_balances_governance(&mut app, govmod_id, instantiate, None);
     let governance_modules: Vec<Addr> = app
         .wrap()
         .query_wasm_smart(
@@ -1937,7 +1843,7 @@ fn test_active_threshold_absolute() {
         deposit_info: None,
     };
 
-    let governance_addr = instantiate_with_staking_default_governance(
+    let governance_addr = instantiate_with_staking_active_threshold(
         &mut app,
         govmod_id,
         instantiate,
@@ -2062,7 +1968,7 @@ fn test_active_threshold_percent() {
     };
 
     // 20% needed to be active, 20% of 100000000 is 20000000
-    let governance_addr = instantiate_with_staking_default_governance(
+    let governance_addr = instantiate_with_staking_active_threshold(
         &mut app,
         govmod_id,
         instantiate,
@@ -2187,7 +2093,7 @@ fn test_active_threshold_none() {
     };
 
     let governance_addr =
-        instantiate_with_staking_default_governance(&mut app, govmod_id, instantiate, None, None);
+        instantiate_with_staking_active_threshold(&mut app, govmod_id, instantiate, None, None);
     let governance_modules: Vec<Addr> = app
         .wrap()
         .query_wasm_smart(
@@ -2264,7 +2170,7 @@ fn test_active_threshold_none() {
     };
 
     let governance_addr =
-        instantiate_with_default_governance(&mut app, govmod_id, instantiate, None);
+        instantiate_with_cw20_balances_governance(&mut app, govmod_id, instantiate, None);
     let governance_modules: Vec<Addr> = app
         .wrap()
         .query_wasm_smart(
diff --git a/contracts/proposal-hooks-counter/src/tests.rs b/contracts/proposal-hooks-counter/src/tests.rs
index 02002f51..095e2309 100644
--- a/contracts/proposal-hooks-counter/src/tests.rs
+++ b/contracts/proposal-hooks-counter/src/tests.rs
@@ -3,13 +3,10 @@ use cw20::Cw20Coin;
 use cw_multi_test::{App, Contract, ContractWrapper, Executor};
 use indexable_hooks::HooksResponse;
 
-use voting::Vote;
+use voting::{PercentageThreshold, Threshold, Vote};
 
 use crate::msg::{CountResponse, InstantiateMsg, QueryMsg};
-use cw_proposal_single::{
-    state::Config,
-    threshold::{PercentageThreshold, Threshold},
-};
+use cw_proposal_single::state::Config;
 
 const CREATOR_ADDR: &str = "creator";
 
diff --git a/packages/testing/Cargo.toml b/packages/testing/Cargo.toml
new file mode 100644
index 00000000..c19f29c8
--- /dev/null
+++ b/packages/testing/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "testing"
+version = "0.1.0"
+edition = "2021"
+
+# This crate depends on multi-test and rand. These are not features in
+# wasm builds of cosmwasm. Despite this crate only being used as a dev
+# dependency, because it is part of the workspace it will always be
+# compiled. There is no good way to remove a member from a workspace
+# conditionally. As such, we don't compile anything here if we're
+# targeting wasm.
+[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
+rand = "0.8"
+cosmwasm-std = { version = "1.0.0-beta5" }
+voting = { path = "../voting", version = "*" }
+cw-multi-test = {  version = "0.13" }
diff --git a/packages/testing/src/lib.rs b/packages/testing/src/lib.rs
new file mode 100644
index 00000000..bc75fb20
--- /dev/null
+++ b/packages/testing/src/lib.rs
@@ -0,0 +1,5 @@
+#[cfg(not(target_arch = "wasm32"))]
+pub mod tests;
+
+#[cfg(not(target_arch = "wasm32"))]
+pub use tests::*;
diff --git a/packages/testing/src/tests.rs b/packages/testing/src/tests.rs
new file mode 100644
index 00000000..a106eece
--- /dev/null
+++ b/packages/testing/src/tests.rs
@@ -0,0 +1,587 @@
+use cosmwasm_std::{Decimal, Uint128};
+use rand::{prelude::SliceRandom, Rng};
+use voting::{PercentageThreshold, Status, Threshold, Vote};
+
+/// If a test vote should execute. Used for fuzzing and checking that
+/// votes after a proposal has completed aren't allowed.
+pub enum ShouldExecute {
+    /// This should execute.
+    Yes,
+    /// This should not execute.
+    No,
+    /// Doesn't matter.
+    Meh,
+}
+
+pub struct TestVote {
+    /// The address casting the vote.
+    pub voter: String,
+    /// Position on the vote.
+    pub position: Vote,
+    /// Voting power of the address.
+    pub weight: Uint128,
+    /// If this vote is expected to execute.
+    pub should_execute: ShouldExecute,
+}
+
+pub fn test_simple_votes<F>(do_votes: F)
+where
+    F: Fn(Vec<TestVote>, Threshold, Status, Option<Uint128>),
+{
+    do_votes(
+        vec![TestVote {
+            voter: "ekez".to_string(),
+            position: Vote::Yes,
+            weight: Uint128::new(10),
+            should_execute: ShouldExecute::Yes,
+        }],
+        Threshold::AbsolutePercentage {
+            percentage: PercentageThreshold::Percent(Decimal::percent(100)),
+        },
+        Status::Passed,
+        None,
+    );
+
+    do_votes(
+        vec![TestVote {
+            voter: "ekez".to_string(),
+            position: Vote::No,
+            weight: Uint128::new(10),
+            should_execute: ShouldExecute::Yes,
+        }],
+        Threshold::AbsolutePercentage {
+            percentage: PercentageThreshold::Percent(Decimal::percent(100)),
+        },
+        Status::Rejected,
+        None,
+    )
+}
+
+pub fn test_simple_vote_no_overflow<F>(do_votes: F)
+where
+    F: Fn(Vec<TestVote>, Threshold, Status, Option<Uint128>),
+{
+    do_votes(
+        vec![TestVote {
+            voter: "ekez".to_string(),
+            position: Vote::Yes,
+            weight: Uint128::new(u128::max_value()),
+            should_execute: ShouldExecute::Yes,
+        }],
+        Threshold::AbsolutePercentage {
+            percentage: PercentageThreshold::Percent(Decimal::percent(100)),
+        },
+        Status::Passed,
+        None,
+    );
+}
+
+pub fn test_vote_no_overflow<F>(do_votes: F)
+where
+    F: Fn(Vec<TestVote>, Threshold, Status, Option<Uint128>),
+{
+    do_votes(
+        vec![TestVote {
+            voter: "ekez".to_string(),
+            position: Vote::Yes,
+            weight: Uint128::new(u128::max_value()),
+            should_execute: ShouldExecute::Yes,
+        }],
+        Threshold::AbsolutePercentage {
+            percentage: PercentageThreshold::Percent(Decimal::percent(100)),
+        },
+        Status::Passed,
+        None,
+    );
+
+    do_votes(
+        vec![
+            TestVote {
+                voter: "zeke".to_string(),
+                position: Vote::No,
+                weight: Uint128::new(1),
+                should_execute: ShouldExecute::Yes,
+            },
+            TestVote {
+                voter: "ekez".to_string(),
+                position: Vote::Yes,
+                weight: Uint128::new(u128::max_value() - 1),
+                should_execute: ShouldExecute::Yes,
+            },
+        ],
+        Threshold::AbsolutePercentage {
+            percentage: PercentageThreshold::Percent(Decimal::percent(99)),
+        },
+        Status::Passed,
+        None,
+    )
+}
+
+pub fn test_simple_early_rejection<F>(do_votes: F)
+where
+    F: Fn(Vec<TestVote>, Threshold, Status, Option<Uint128>),
+{
+    do_votes(
+        vec![
+            TestVote {
+                voter: "zeke".to_string(),
+                position: Vote::No,
+                weight: Uint128::new(1),
+                should_execute: ShouldExecute::Yes,
+            },
+            TestVote {
+                voter: "ekez".to_string(),
+                position: Vote::Yes,
+                weight: Uint128::new(u128::max_value() - 1),
+                should_execute: ShouldExecute::No,
+            },
+        ],
+        Threshold::AbsolutePercentage {
+            percentage: PercentageThreshold::Percent(Decimal::percent(100)),
+        },
+        Status::Rejected,
+        None,
+    );
+
+    do_votes(
+        vec![TestVote {
+            voter: "ekez".to_string(),
+            position: Vote::No,
+            weight: Uint128::new(1),
+            should_execute: ShouldExecute::Yes,
+        }],
+        Threshold::AbsolutePercentage {
+            percentage: PercentageThreshold::Percent(Decimal::percent(99)),
+        },
+        Status::Open,
+        Some(Uint128::from(u128::max_value())),
+    );
+}
+
+pub fn test_vote_abstain_only<F>(do_votes: F)
+where
+    F: Fn(Vec<TestVote>, Threshold, Status, Option<Uint128>),
+{
+    do_votes(
+        vec![TestVote {
+            voter: "ekez".to_string(),
+            position: Vote::Abstain,
+            weight: Uint128::new(u64::max_value().into()),
+            should_execute: ShouldExecute::Yes,
+        }],
+        Threshold::AbsolutePercentage {
+            percentage: PercentageThreshold::Percent(Decimal::percent(100)),
+        },
+        Status::Rejected,
+        None,
+    );
+
+    // The quorum shouldn't matter here in determining if the vote is
+    // rejected.
+    for i in 0..101 {
+        do_votes(
+            vec![TestVote {
+                voter: "ekez".to_string(),
+                position: Vote::Abstain,
+                weight: Uint128::new(u64::max_value().into()),
+                should_execute: ShouldExecute::Yes,
+            }],
+            Threshold::ThresholdQuorum {
+                threshold: PercentageThreshold::Percent(Decimal::percent(100)),
+                quorum: PercentageThreshold::Percent(Decimal::percent(i)),
+            },
+            Status::Rejected,
+            None,
+        );
+    }
+}
+
+pub fn test_tricky_rounding<F>(do_votes: F)
+where
+    F: Fn(Vec<TestVote>, Threshold, Status, Option<Uint128>),
+{
+    // This tests the smallest possible round up for passing
+    // thresholds we can have. Specifically, a 1% passing threshold
+    // and 1 total vote. This should round up and only pass if there
+    // are more than 1 yes votes.
+    do_votes(
+        vec![TestVote {
+            voter: "ekez".to_string(),
+            position: Vote::Yes,
+            weight: Uint128::new(1),
+            should_execute: ShouldExecute::Yes,
+        }],
+        Threshold::AbsolutePercentage {
+            percentage: PercentageThreshold::Percent(Decimal::percent(1)),
+        },
+        Status::Passed,
+        Some(Uint128::new(100)),
+    );
+
+    do_votes(
+        vec![TestVote {
+            voter: "ekez".to_string(),
+            position: Vote::Yes,
+            weight: Uint128::new(10),
+            should_execute: ShouldExecute::Yes,
+        }],
+        Threshold::AbsolutePercentage {
+            percentage: PercentageThreshold::Percent(Decimal::percent(1)),
+        },
+        Status::Passed,
+        Some(Uint128::new(1000)),
+    );
+
+    // HIGH PERCISION
+    do_votes(
+        vec![TestVote {
+            voter: "ekez".to_string(),
+            position: Vote::Yes,
+            weight: Uint128::new(9999999),
+            should_execute: ShouldExecute::Yes,
+        }],
+        Threshold::AbsolutePercentage {
+            percentage: PercentageThreshold::Percent(Decimal::percent(1)),
+        },
+        Status::Open,
+        Some(Uint128::new(1000000000)),
+    );
+
+    do_votes(
+        vec![TestVote {
+            voter: "ekez".to_string(),
+            position: Vote::Abstain,
+            weight: Uint128::new(1),
+            should_execute: ShouldExecute::Yes,
+        }],
+        Threshold::AbsolutePercentage {
+            percentage: PercentageThreshold::Percent(Decimal::percent(1)),
+        },
+        Status::Rejected,
+        None,
+    );
+}
+
+pub fn test_no_double_votes<F>(do_votes: F)
+where
+    F: Fn(Vec<TestVote>, Threshold, Status, Option<Uint128>),
+{
+    do_votes(
+        vec![
+            TestVote {
+                voter: "ekez".to_string(),
+                position: Vote::Abstain,
+                weight: Uint128::new(2),
+                should_execute: ShouldExecute::Yes,
+            },
+            TestVote {
+                voter: "ekez".to_string(),
+                position: Vote::Yes,
+                weight: Uint128::new(2),
+                should_execute: ShouldExecute::No,
+            },
+        ],
+        Threshold::AbsolutePercentage {
+            percentage: PercentageThreshold::Percent(Decimal::percent(100)),
+        },
+        // NOTE: Updating our cw20-base version will cause this to
+        // fail. In versions of cw20-base before Feb 15 2022 (the one
+        // we use at the time of writing) it was allowed to have an
+        // initial balance that repeats for a given address but it
+        // would cause miscalculation of the total supply. In this
+        // case the total supply is miscumputed to be 4 so this is
+        // assumed to have 2 abstain votes out of 4 possible votes.
+        Status::Open,
+        Some(Uint128::new(10)),
+    )
+}
+
+pub fn test_votes_favor_yes<F>(do_votes: F)
+where
+    F: Fn(Vec<TestVote>, Threshold, Status, Option<Uint128>),
+{
+    do_votes(
+        vec![
+            TestVote {
+                voter: "ekez".to_string(),
+                position: Vote::Abstain,
+                weight: Uint128::new(10),
+                should_execute: ShouldExecute::Yes,
+            },
+            TestVote {
+                voter: "keze".to_string(),
+                position: Vote::No,
+                weight: Uint128::new(5),
+                should_execute: ShouldExecute::Yes,
+            },
+            TestVote {
+                voter: "ezek".to_string(),
+                position: Vote::Yes,
+                weight: Uint128::new(5),
+                should_execute: ShouldExecute::Yes,
+            },
+        ],
+        Threshold::AbsolutePercentage {
+            percentage: PercentageThreshold::Percent(Decimal::percent(50)),
+        },
+        Status::Passed,
+        None,
+    );
+
+    do_votes(
+        vec![
+            TestVote {
+                voter: "ekez".to_string(),
+                position: Vote::Abstain,
+                weight: Uint128::new(10),
+                should_execute: ShouldExecute::Yes,
+            },
+            TestVote {
+                voter: "keze".to_string(),
+                position: Vote::Yes,
+                weight: Uint128::new(5),
+                should_execute: ShouldExecute::Yes,
+            },
+            TestVote {
+                voter: "ezek".to_string(),
+                position: Vote::No,
+                weight: Uint128::new(5),
+                should_execute: ShouldExecute::No,
+            },
+        ],
+        Threshold::AbsolutePercentage {
+            percentage: PercentageThreshold::Percent(Decimal::percent(50)),
+        },
+        Status::Passed,
+        None,
+    );
+}
+
+pub fn test_votes_low_threshold<F>(do_votes: F)
+where
+    F: Fn(Vec<TestVote>, Threshold, Status, Option<Uint128>),
+{
+    do_votes(
+        vec![
+            TestVote {
+                voter: "ekez".to_string(),
+                position: Vote::No,
+                weight: Uint128::new(10),
+                should_execute: ShouldExecute::Yes,
+            },
+            TestVote {
+                voter: "keze".to_string(),
+                position: Vote::Yes,
+                weight: Uint128::new(5),
+                should_execute: ShouldExecute::Yes,
+            },
+            TestVote {
+                voter: "ezek".to_string(),
+                position: Vote::No,
+                weight: Uint128::new(10),
+                should_execute: ShouldExecute::No,
+            },
+        ],
+        Threshold::ThresholdQuorum {
+            threshold: PercentageThreshold::Percent(Decimal::percent(10)),
+            quorum: PercentageThreshold::Majority {},
+        },
+        Status::Passed,
+        None,
+    );
+}
+
+pub fn test_majority_vs_half<F>(do_votes: F)
+where
+    F: Fn(Vec<TestVote>, Threshold, Status, Option<Uint128>),
+{
+    do_votes(
+        vec![
+            TestVote {
+                voter: "ekez".to_string(),
+                position: Vote::No,
+                weight: Uint128::new(10),
+                should_execute: ShouldExecute::Yes,
+            },
+            TestVote {
+                voter: "keze".to_string(),
+                position: Vote::Yes,
+                weight: Uint128::new(10),
+                should_execute: ShouldExecute::Yes,
+            },
+        ],
+        Threshold::ThresholdQuorum {
+            threshold: PercentageThreshold::Percent(Decimal::percent(50)),
+            quorum: PercentageThreshold::Majority {},
+        },
+        Status::Passed,
+        None,
+    );
+
+    do_votes(
+        vec![
+            TestVote {
+                voter: "ekez".to_string(),
+                position: Vote::No,
+                weight: Uint128::new(10),
+                should_execute: ShouldExecute::Yes,
+            },
+            TestVote {
+                voter: "keze".to_string(),
+                position: Vote::Yes,
+                weight: Uint128::new(10),
+                should_execute: ShouldExecute::No,
+            },
+        ],
+        Threshold::ThresholdQuorum {
+            threshold: PercentageThreshold::Majority {},
+            quorum: PercentageThreshold::Majority {},
+        },
+        Status::Rejected,
+        None,
+    );
+}
+
+pub fn test_pass_threshold_not_quorum<F>(do_votes: F)
+where
+    F: Fn(Vec<TestVote>, Threshold, Status, Option<Uint128>),
+{
+    do_votes(
+        vec![TestVote {
+            voter: "ekez".to_string(),
+            position: Vote::Yes,
+            weight: Uint128::new(59),
+            should_execute: ShouldExecute::Yes,
+        }],
+        Threshold::ThresholdQuorum {
+            threshold: PercentageThreshold::Majority {},
+            quorum: PercentageThreshold::Percent(Decimal::percent(60)),
+        },
+        Status::Open,
+        Some(Uint128::new(100)),
+    );
+    do_votes(
+        vec![TestVote {
+            voter: "ekez".to_string(),
+            position: Vote::No,
+            weight: Uint128::new(59),
+            should_execute: ShouldExecute::Yes,
+        }],
+        Threshold::ThresholdQuorum {
+            threshold: PercentageThreshold::Majority {},
+            quorum: PercentageThreshold::Percent(Decimal::percent(60)),
+        },
+        // As the threshold is 50% and 59% of voters have voted no
+        // this is unable to pass.
+        Status::Rejected,
+        Some(Uint128::new(100)),
+    );
+}
+
+pub fn test_pass_exactly_quorum<F>(do_votes: F)
+where
+    F: Fn(Vec<TestVote>, Threshold, Status, Option<Uint128>),
+{
+    do_votes(
+        vec![TestVote {
+            voter: "ekez".to_string(),
+            position: Vote::Yes,
+            weight: Uint128::new(60),
+            should_execute: ShouldExecute::Yes,
+        }],
+        Threshold::ThresholdQuorum {
+            threshold: PercentageThreshold::Majority {},
+            quorum: PercentageThreshold::Percent(Decimal::percent(60)),
+        },
+        Status::Passed,
+        Some(Uint128::new(100)),
+    );
+    do_votes(
+        vec![
+            TestVote {
+                voter: "ekez".to_string(),
+                position: Vote::Yes,
+                weight: Uint128::new(59),
+                should_execute: ShouldExecute::Yes,
+            },
+            // This is an intersting one because in this case the no
+            // voter is actually incentivised not to vote. By voting
+            // they move the quorum over the threshold and pass the
+            // vote. In a DAO with sufficently involved stakeholders
+            // no voters should effectively never vote if there is a
+            // quorum higher than the threshold as it makes the
+            // passing threshold the quorum threshold.
+            TestVote {
+                voter: "keze".to_string(),
+                position: Vote::No,
+                weight: Uint128::new(1),
+                should_execute: ShouldExecute::Yes,
+            },
+        ],
+        Threshold::ThresholdQuorum {
+            threshold: PercentageThreshold::Majority {},
+            quorum: PercentageThreshold::Percent(Decimal::percent(60)),
+        },
+        Status::Passed,
+        Some(Uint128::new(100)),
+    );
+    do_votes(
+        vec![TestVote {
+            voter: "ekez".to_string(),
+            position: Vote::No,
+            weight: Uint128::new(60),
+            should_execute: ShouldExecute::Yes,
+        }],
+        Threshold::ThresholdQuorum {
+            threshold: PercentageThreshold::Majority {},
+            quorum: PercentageThreshold::Percent(Decimal::percent(60)),
+        },
+        Status::Rejected,
+        Some(Uint128::new(100)),
+    );
+}
+
+pub fn fuzz_voting<F>(do_votes: F)
+where
+    F: Fn(Vec<TestVote>, Threshold, Status, Option<Uint128>),
+{
+    let mut rng = rand::thread_rng();
+    let dist = rand::distributions::Uniform::<u64>::new(1, 200);
+    for _ in 0..10 {
+        let yes: Vec<u64> = (0..50).map(|_| rng.sample(&dist)).collect();
+        let no: Vec<u64> = (0..50).map(|_| rng.sample(&dist)).collect();
+
+        let yes_sum: u64 = yes.iter().sum();
+        let no_sum: u64 = no.iter().sum();
+        let expected_status = match yes_sum.cmp(&no_sum) {
+            std::cmp::Ordering::Less => Status::Rejected,
+            // Depends on which reaches the threshold first. Ignore for now.
+            std::cmp::Ordering::Equal => Status::Rejected,
+            std::cmp::Ordering::Greater => Status::Passed,
+        };
+
+        let yes = yes.into_iter().enumerate().map(|(idx, weight)| TestVote {
+            voter: format!("yes_{}", idx),
+            position: Vote::Yes,
+            weight: Uint128::new(weight as u128),
+            should_execute: ShouldExecute::Meh,
+        });
+        let no = no.into_iter().enumerate().map(|(idx, weight)| TestVote {
+            voter: format!("no_{}", idx),
+            position: Vote::No,
+            weight: Uint128::new(weight as u128),
+            should_execute: ShouldExecute::Meh,
+        });
+        let mut votes = yes.chain(no).collect::<Vec<_>>();
+        votes.shuffle(&mut rng);
+
+        do_votes(
+            votes,
+            Threshold::AbsolutePercentage {
+                percentage: PercentageThreshold::Majority {},
+            },
+            expected_status,
+            None,
+        );
+    }
+}