diff --git a/Cargo.lock b/Cargo.lock index 4f9cc4309..6565c11db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -257,9 +257,9 @@ dependencies = [ [[package]] name = "bnum" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128a44527fc0d6abf05f9eda748b9027536e12dff93f5acc8449f51583309350" +checksum = "ab9008b6bb9fc80b5277f2fe481c09e828743d9151203e804583eb4c9e15b31d" [[package]] name = "bootstrap-env" @@ -269,8 +269,8 @@ dependencies = [ "cosm-orc", "cosmwasm-std", "cw-admin-factory", - "cw-utils 1.0.2", - "cw20 1.1.1", + "cw-utils 1.0.3", + "cw20 1.1.2", "cw20-stake 2.3.0", "dao-dao-core", "dao-interface", @@ -360,9 +360,9 @@ dependencies = [ [[package]] name = "config" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" +checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca" dependencies = [ "async-trait", "json5", @@ -385,9 +385,9 @@ checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -395,9 +395,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cosm-orc" @@ -670,9 +670,9 @@ dependencies = [ "cosmwasm-storage", "cw-multi-test", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw20-base 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20-base 1.1.2", "dao-dao-core", "dao-interface", "thiserror", @@ -708,14 +708,14 @@ dependencies = [ [[package]] name = "cw-controllers" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b129ca74fa41111fd2e1727426532556dc63973420343b659f5c072b85d789" +checksum = "57de8d3761e46be863e3ac1eba8c8a976362a48c6abf240df1e26c3e421ee9e8" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", + "cw-utils 1.0.3", "schemars", "serde", "thiserror", @@ -795,8 +795,8 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw20 1.1.1", - "cw20-base 1.1.1", + "cw20 1.1.2", + "cw20-base 1.1.2", "thiserror", ] @@ -809,10 +809,10 @@ dependencies = [ "cw-multi-test", "cw-paginate-storage 2.3.0", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw20 1.1.1", - "cw20-base 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20 1.1.2", + "cw20-base 1.1.2", "cw20-stake 2.3.0", "dao-dao-core", "dao-interface", @@ -839,10 +839,10 @@ dependencies = [ "anyhow", "cosmwasm-std", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", + "cw-utils 1.0.3", "derivative", "itertools 0.11.0", - "prost 0.12.2", + "prost 0.12.3", "schemars", "serde", "sha2 0.10.8", @@ -860,7 +860,7 @@ dependencies = [ "cw-address-like", "cw-ownable-derive", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", + "cw-utils 1.0.3", "thiserror", ] @@ -908,11 +908,11 @@ dependencies = [ "cw-multi-test", "cw-ownable", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", + "cw-utils 1.0.3", "cw-vesting", - "cw2 1.1.1", - "cw20 1.1.1", - "cw20-base 1.1.1", + "cw2 1.1.2", + "cw20 1.1.2", + "cw20-base 1.1.2", "thiserror", "wynd-utils", ] @@ -1003,10 +1003,10 @@ dependencies = [ "cosmwasm-std", "cw-multi-test", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw20 1.1.1", - "cw20-base 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20 1.1.2", + "cw20-base 1.1.2", "thiserror", ] @@ -1020,7 +1020,7 @@ dependencies = [ "cw-multi-test", "cw-ownable", "cw-storage-plus 1.2.0", - "cw2 1.1.1", + "cw2 1.1.2", "osmosis-std", "osmosis-test-tube", "prost 0.11.9", @@ -1072,13 +1072,13 @@ dependencies = [ [[package]] name = "cw-utils" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9f351a4e4d81ef7c890e44d903f8c0bdcdc00f094fd3a181eaf70c0eec7a3a" +checksum = "1c4a657e5caacc3a0d00ee96ca8618745d050b8f757c709babafb81208d4239c" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw2 1.1.1", + "cw2 1.1.2", "schemars", "semver", "serde", @@ -1098,11 +1098,11 @@ dependencies = [ "cw-paginate-storage 2.3.0", "cw-stake-tracker", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", + "cw-utils 1.0.3", "cw-wormhole", - "cw2 1.1.1", - "cw20 1.1.1", - "cw20-base 1.1.1", + "cw2 1.1.2", + "cw20 1.1.2", + "cw20-base 1.1.2", "dao-testing", "serde", "thiserror", @@ -1158,14 +1158,15 @@ dependencies = [ [[package]] name = "cw2" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9431d14f64f49e41c6ef5561ed11a5391c417d0cb16455dea8cdcb9037a8d197" +checksum = "c6c120b24fbbf5c3bedebb97f2cc85fbfa1c3287e09223428e7e597b5293c1fa" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", "schemars", + "semver", "serde", "thiserror", ] @@ -1196,13 +1197,13 @@ dependencies = [ [[package]] name = "cw20" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786e9da5e937f473cecd2463e81384c1af65d0f6398bbd851be7655487c55492" +checksum = "526e39bb20534e25a1cd0386727f0038f4da294e5e535729ba3ef54055246abd" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-utils 1.0.2", + "cw-utils 1.0.3", "schemars", "serde", ] @@ -1241,16 +1242,15 @@ dependencies = [ [[package]] name = "cw20-base" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09558f87fd3d5e4a479761051b3f98ee2fa723d9e484b5679b6058ad0eadf8f1" +checksum = "17ad79e86ea3707229bf78df94e08732e8f713207b4a77b2699755596725e7d9" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw20 1.1.1", + "cw2 1.1.2", + "cw20 1.1.2", "schemars", "semver", "serde", @@ -1284,17 +1284,17 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", - "cw-controllers 1.1.1", + "cw-controllers 1.1.2", "cw-hooks", "cw-multi-test", "cw-ownable", "cw-paginate-storage 2.3.0", "cw-storage-plus 1.2.0", "cw-utils 0.13.4", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw20 1.1.1", - "cw20-base 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20 1.1.2", + "cw20-base 1.1.2", "cw20-stake 0.2.6", "dao-hooks", "dao-voting 2.3.0", @@ -1309,15 +1309,15 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", - "cw-controllers 1.1.1", + "cw-controllers 1.1.2", "cw-multi-test", "cw-ownable", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", "cw20 0.13.4", - "cw20 1.1.1", - "cw20-base 1.1.1", + "cw20 1.1.2", + "cw20-base 1.1.2", "cw20-stake 2.3.0", "dao-hooks", "stake-cw20-external-rewards", @@ -1333,10 +1333,10 @@ dependencies = [ "cw-multi-test", "cw-ownable", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw20 1.1.1", - "cw20-base 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20 1.1.2", + "cw20-base 1.1.2", "cw20-stake 2.3.0", "stake-cw20-reward-distributor", "thiserror", @@ -1377,14 +1377,14 @@ dependencies = [ [[package]] name = "cw3" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d056ec33ec146554aa1d16c9535763341db75589a47743c006c377e62b54034" +checksum = "2967fbd073d4b626dd9e7148e05a84a3bebd9794e71342e12351110ffbb12395" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-utils 1.0.2", - "cw20 1.1.1", + "cw-utils 1.0.3", + "cw20 1.1.2", "schemars", "serde", "thiserror", @@ -1404,9 +1404,9 @@ dependencies = [ [[package]] name = "cw4" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d9fce5c21c0623762a78c658a01cd5053dc98705b71453685359554b423d8a6" +checksum = "24754ff6e45f2a1c60adc409d9b2eb87666012c44021329141ffaab3388fccd2" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1434,17 +1434,17 @@ dependencies = [ [[package]] name = "cw4-group" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad498a23264ee2f24e7e15b56295b23a82e7167e6fbf2564d8c818f6e75ae9e7" +checksum = "9e24a22c3af54c52edf528673b420a67a1648be2c159b8ec778d2fbf543df24b" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-controllers 1.1.1", + "cw-controllers 1.1.2", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw4 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw4 1.1.2", "schemars", "serde", "thiserror", @@ -1502,7 +1502,7 @@ checksum = "e3c4d286625ccadc957fe480dd3bdc54ada19e0e6b5b9325379db3130569e914" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-utils 1.0.2", + "cw-utils 1.0.3", "schemars", "serde", ] @@ -1534,8 +1534,8 @@ dependencies = [ "cosmwasm-std", "cw-ownable", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", "cw721 0.18.0", "cw721-base 0.16.0", "schemars", @@ -1550,7 +1550,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", + "cw-utils 1.0.3", "thiserror", ] @@ -1560,13 +1560,13 @@ version = "2.3.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-controllers 1.1.1", + "cw-controllers 1.1.2", "cw-multi-test", "cw-ownable", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw4 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw4 1.1.2", "cw721 0.18.0", "cw721-base 0.18.0", "dao-cw721-extensions", @@ -1582,8 +1582,8 @@ version = "2.3.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-controllers 1.1.1", - "cw4 1.1.1", + "cw-controllers 1.1.2", + "cw4 1.1.2", ] [[package]] @@ -1596,10 +1596,10 @@ dependencies = [ "cw-multi-test", "cw-paginate-storage 2.3.0", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw20 1.1.1", - "cw20-base 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20 1.1.2", + "cw20-base 1.1.2", "cw721 0.18.0", "cw721-base 0.18.0", "dao-dao-macros", @@ -1630,7 +1630,8 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-hooks", - "cw4 1.1.1", + "cw4 1.1.2", + "dao-pre-propose-base", "dao-voting 2.3.0", ] @@ -1641,9 +1642,9 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-hooks", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw20 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20 1.1.2", "cw721 0.18.0", "osmosis-std", ] @@ -1661,11 +1662,11 @@ dependencies = [ "cw-proposal-single", "cw-storage-plus 1.2.0", "cw-utils 0.13.4", - "cw-utils 1.0.2", - "cw2 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", "cw20 0.13.4", - "cw20 1.1.1", - "cw20-base 1.1.1", + "cw20 1.1.2", + "cw20-base 1.1.2", "cw20-stake 0.2.6", "cw20-stake 2.3.0", "cw20-staked-balance-voting", @@ -1692,11 +1693,11 @@ dependencies = [ "cw-multi-test", "cw-paginate-storage 2.3.0", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw20 1.1.1", - "cw20-base 1.1.1", - "cw4-group 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20 1.1.2", + "cw20-base 1.1.2", + "cw4-group 1.1.2", "dao-dao-core", "dao-hooks", "dao-interface", @@ -1718,11 +1719,11 @@ dependencies = [ "cw-denom", "cw-multi-test", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw20 1.1.1", - "cw20-base 1.1.1", - "cw4-group 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20 1.1.2", + "cw20-base 1.1.2", + "cw4-group 1.1.2", "dao-dao-core", "dao-hooks", "dao-interface", @@ -1745,9 +1746,8 @@ dependencies = [ "cw-hooks", "cw-multi-test", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", - "dao-hooks", + "cw-utils 1.0.3", + "cw2 1.1.2", "dao-interface", "dao-voting 2.3.0", "serde", @@ -1762,11 +1762,11 @@ dependencies = [ "cosmwasm-std", "cw-denom", "cw-multi-test", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw20 1.1.1", - "cw20-base 1.1.1", - "cw4-group 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20 1.1.2", + "cw20-base 1.1.2", + "cw4-group 1.1.2", "dao-dao-core", "dao-hooks", "dao-interface", @@ -1787,11 +1787,11 @@ dependencies = [ "cw-denom", "cw-hooks", "cw-multi-test", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw20 1.1.1", - "cw20-base 1.1.1", - "cw4-group 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20 1.1.2", + "cw20-base 1.1.2", + "cw4-group 1.1.2", "dao-dao-core", "dao-hooks", "dao-interface", @@ -1812,10 +1812,10 @@ dependencies = [ "cosmwasm-std", "cw-multi-test", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw4 1.1.1", - "cw4-group 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw4 1.1.2", + "cw4-group 1.1.2", "dao-dao-core", "dao-dao-macros", "dao-interface", @@ -1834,10 +1834,10 @@ dependencies = [ "cw-hooks", "cw-multi-test", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw20 1.1.1", - "cw20-base 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20 1.1.2", + "cw20-base 1.1.2", "dao-dao-core", "dao-hooks", "dao-interface", @@ -1851,6 +1851,7 @@ dependencies = [ name = "dao-proposal-multiple" version = "2.3.0" dependencies = [ + "anyhow", "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", @@ -1858,14 +1859,14 @@ dependencies = [ "cw-hooks", "cw-multi-test", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw20 1.1.1", - "cw20-base 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20 1.1.2", + "cw20-base 1.1.2", "cw20-stake 2.3.0", - "cw3 1.1.1", - "cw4 1.1.1", - "cw4-group 1.1.1", + "cw3 1.1.2", + "cw4 1.1.2", + "cw4-group 1.1.2", "cw721-base 0.18.0", "dao-dao-macros", "dao-hooks", @@ -1888,6 +1889,7 @@ dependencies = [ name = "dao-proposal-single" version = "2.3.0" dependencies = [ + "anyhow", "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", @@ -1898,14 +1900,14 @@ dependencies = [ "cw-proposal-single", "cw-storage-plus 1.2.0", "cw-utils 0.13.4", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw20 1.1.1", - "cw20-base 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20 1.1.2", + "cw20-base 1.1.2", "cw20-stake 2.3.0", - "cw3 1.1.1", - "cw4 1.1.1", - "cw4-group 1.1.1", + "cw3 1.1.2", + "cw4 1.1.2", + "cw4-group 1.1.2", "cw721-base 0.18.0", "dao-dao-core", "dao-dao-macros", @@ -1933,7 +1935,7 @@ dependencies = [ "cosmwasm-storage", "cw-multi-test", "cw-storage-plus 1.2.0", - "cw2 1.1.1", + "cw2 1.1.2", "dao-dao-macros", "dao-interface", "thiserror", @@ -1950,8 +1952,8 @@ dependencies = [ "cw-ownable", "cw-storage-plus 1.2.0", "cw-tokenfactory-issuer", - "cw-utils 1.0.2", - "cw2 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", "cw721 0.18.0", "cw721-base 0.18.0", "dao-dao-macros", @@ -1971,14 +1973,14 @@ dependencies = [ "cw-multi-test", "cw-proposal-single", "cw-tokenfactory-issuer", - "cw-utils 1.0.2", + "cw-utils 1.0.3", "cw-vesting", - "cw2 1.1.1", - "cw20 1.1.1", - "cw20-base 1.1.1", + "cw2 1.1.2", + "cw20 1.1.2", + "cw20-base 1.1.2", "cw20-stake 2.3.0", - "cw4 1.1.1", - "cw4-group 1.1.1", + "cw4 1.1.2", + "cw4-group 1.1.2", "cw721-base 0.18.0", "cw721-roles", "dao-dao-core", @@ -2025,8 +2027,8 @@ dependencies = [ "cosmwasm-std", "cw-denom", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw20 1.1.1", + "cw-utils 1.0.3", + "cw20 1.1.2", "dao-dao-macros", "dao-interface", "thiserror", @@ -2040,10 +2042,10 @@ dependencies = [ "cosmwasm-std", "cw-multi-test", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw20 1.1.1", - "cw20-base 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20 1.1.2", + "cw20-base 1.1.2", "dao-dao-macros", "dao-interface", "thiserror", @@ -2058,10 +2060,10 @@ dependencies = [ "cosmwasm-storage", "cw-multi-test", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw20 1.1.1", - "cw20-base 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20 1.1.2", + "cw20-base 1.1.2", "cw20-stake 2.3.0", "dao-dao-macros", "dao-interface", @@ -2078,10 +2080,10 @@ dependencies = [ "cosmwasm-storage", "cw-multi-test", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw4 1.1.1", - "cw4-group 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw4 1.1.2", + "cw4-group 1.1.2", "dao-dao-macros", "dao-interface", "thiserror", @@ -2098,9 +2100,9 @@ dependencies = [ "cw-ownable", "cw-paginate-storage 2.3.0", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", - "cw4 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw4 1.1.2", "cw721 0.18.0", "cw721-base 0.18.0", "cw721-controllers", @@ -2119,13 +2121,13 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", - "cw-controllers 1.1.1", + "cw-controllers 1.1.2", "cw-hooks", "cw-multi-test", "cw-paginate-storage 2.3.0", "cw-storage-plus 1.2.0", - "cw-utils 1.0.2", - "cw2 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", "cw721 0.18.0", "cw721-base 0.18.0", "cw721-controllers", @@ -2151,15 +2153,15 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", - "cw-controllers 1.1.1", + "cw-controllers 1.1.2", "cw-hooks", "cw-multi-test", "cw-ownable", "cw-paginate-storage 2.3.0", "cw-storage-plus 1.2.0", "cw-tokenfactory-issuer", - "cw-utils 1.0.2", - "cw2 1.1.1", + "cw-utils 1.0.3", + "cw2 1.1.2", "dao-dao-macros", "dao-hooks", "dao-interface", @@ -2261,7 +2263,7 @@ dependencies = [ "elliptic-curve 0.13.8", "rfc6979 0.4.0", "signature 2.2.0", - "spki 0.7.2", + "spki 0.7.3", ] [[package]] @@ -2375,12 +2377,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -2431,9 +2433,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -2559,9 +2561,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "glob" @@ -2621,9 +2623,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "headers" @@ -2676,7 +2678,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2794,9 +2796,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -2838,7 +2840,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.2", + "hashbrown 0.14.3", ] [[package]] @@ -2851,10 +2853,10 @@ dependencies = [ "cosm-tome", "cosmos-sdk-proto 0.19.0", "cosmwasm-std", - "cw-utils 1.0.2", + "cw-utils 1.0.3", "cw-vesting", - "cw20 1.1.1", - "cw20-base 1.1.1", + "cw20 1.1.2", + "cw20-base 1.1.2", "cw20-stake 2.3.0", "cw721 0.18.0", "cw721-base 0.18.0", @@ -2883,7 +2885,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2912,9 +2914,9 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] @@ -3002,9 +3004,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "log" @@ -3053,7 +3055,7 @@ checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3245,9 +3247,9 @@ checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" @@ -3343,7 +3345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der 0.7.8", - "spki 0.7.2", + "spki 0.7.3", ] [[package]] @@ -3364,9 +3366,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -3395,12 +3397,12 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5a410fc7882af66deb8d01d01737353cf3ad6204c408177ba494291a626312" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" dependencies = [ "bytes", - "prost-derive 0.12.2", + "prost-derive 0.12.3", ] [[package]] @@ -3418,9 +3420,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065717a5dfaca4a83d2fe57db3487b311365200000551d7a364e715dbf4346bc" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" dependencies = [ "anyhow", "itertools 0.11.0", @@ -3603,15 +3605,15 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.25" +version = "0.38.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" +checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a" dependencies = [ "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -3666,7 +3668,7 @@ version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3762,9 +3764,9 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "serde" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] @@ -3798,9 +3800,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", @@ -3950,7 +3952,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3971,9 +3973,9 @@ dependencies = [ [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der 0.7.8", @@ -4421,7 +4423,7 @@ dependencies = [ "pin-project-lite", "socket2 0.5.5", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -4676,9 +4678,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -4736,9 +4738,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -4746,9 +4748,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", @@ -4761,9 +4763,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4771,9 +4773,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", @@ -4784,15 +4786,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ "js-sys", "wasm-bindgen", @@ -4866,7 +4868,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -4875,13 +4886,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -4890,42 +4916,84 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "wynd-utils" version = "0.4.1" diff --git a/Cargo.toml b/Cargo.toml index f711aa48f..f87c6a177 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,13 +116,13 @@ dao-voting-cw721-staked = {path = "./contracts/voting/dao-voting-cw721-staked", dao-voting-token-staked = {path = "./contracts/voting/dao-voting-token-staked", version = "2.3.0"} # v1 dependencies. used for state migrations. -cw-core-v1 = {package = "cw-core", version = "0.1.0"} -cw-proposal-single-v1 = {package = "cw-proposal-single", version = "0.1.0"} -cw-utils-v1 = {package = "cw-utils", version = "0.13"} -cw20-stake-external-rewards-v1 = {package = "stake-cw20-external-rewards", version = "0.2.6"} -cw20-stake-reward-distributor-v1 = {package = "stake-cw20-reward-distributor", version = "0.1.0"} -cw20-stake-v1 = {package = "cw20-stake", version = "0.2.6"} -cw20-staked-balance-voting-v1 = {package = "cw20-staked-balance-voting", version = "0.1.0"} -cw4-voting-v1 = {package = "cw4-voting", version = "0.1.0"} -stake-cw20-v03 = {package = "stake-cw20", version = "0.2.6"} -voting-v1 = {package = "dao-voting", version = "0.1.0"} +cw-core-v1 = { package = "cw-core", version = "0.1.0" } +cw-proposal-single-v1 = { package = "cw-proposal-single", version = "0.1.0" } +cw-utils-v1 = { package = "cw-utils", version = "0.13" } +cw20-stake-external-rewards-v1 = { package = "stake-cw20-external-rewards", version = "0.2.6" } +cw20-stake-reward-distributor-v1 = { package = "stake-cw20-reward-distributor", version = "0.1.0" } +cw20-stake-v1 = { package = "cw20-stake", version = "0.2.6" } +cw20-staked-balance-voting-v1 = { package = "cw20-staked-balance-voting", version = "0.1.0" } +cw4-voting-v1 = { package = "cw4-voting", version = "0.1.0" } +stake-cw20-v03 = { package = "stake-cw20", version = "0.2.6" } +voting-v1 = { package = "dao-voting", version = "0.1.0" } diff --git a/ci/bootstrap-env/src/main.rs b/ci/bootstrap-env/src/main.rs index d8117c8f5..809b1f5a8 100644 --- a/ci/bootstrap-env/src/main.rs +++ b/ci/bootstrap-env/src/main.rs @@ -107,6 +107,7 @@ fn main() -> Result<()> { }, }, close_proposal_on_execution_failure: false, + veto: None, })?, admin: Some(Admin::CoreModule {}), funds: vec![], diff --git a/ci/integration-tests/src/helpers/helper.rs b/ci/integration-tests/src/helpers/helper.rs index e4f4678cf..772025e4c 100644 --- a/ci/integration-tests/src/helpers/helper.rs +++ b/ci/integration-tests/src/helpers/helper.rs @@ -91,6 +91,7 @@ pub fn create_dao( label: "DAO DAO Pre-Propose Module".to_string(), }, }, + veto: None, })?, admin: Some(Admin::CoreModule {}), funds: vec![], diff --git a/contracts/external/dao-migrator/Cargo.toml b/contracts/external/dao-migrator/Cargo.toml index 346ec7f6f..4d2b48cb8 100644 --- a/contracts/external/dao-migrator/Cargo.toml +++ b/contracts/external/dao-migrator/Cargo.toml @@ -26,7 +26,7 @@ cw2 = { workspace = true } cw20 = { workspace = true } dao-interface = { workspace = true } -dao-dao-core = { workspace = true, features = ["library"] } +dao-dao-core = { workspace = true, features = ["library"] } dao-voting = { workspace = true } dao-proposal-single = { workspace = true, features = ["library"] } dao-voting-cw4 = { workspace = true, features = ["library"] } @@ -43,7 +43,7 @@ cw20-stake-v1 = { workspace = true, features = ["library"] } cw-core-interface-v1 = { package = "cw-core-interface", version = "0.1.0", git = "https://github.com/DA0-DA0/dao-contracts.git", tag = "v1.0.0" } cw4-voting-v1 = { package = "cw4-voting", version = "0.1.0", git = "https://github.com/DA0-DA0/dao-contracts.git", tag = "v1.0.0" } cw20-v1 = { version = "0.13", package = "cw20" } -cw4-v1 = { version = "0.13", package = "cw4" } +cw4-v1 = { version = "0.13", package = "cw4" } [dev-dependencies] cosmwasm-schema = { workspace = true } diff --git a/contracts/external/dao-migrator/examples/schema.rs b/contracts/external/dao-migrator/examples/schema.rs index 9a8dfd2c1..8b7a1300e 100644 --- a/contracts/external/dao-migrator/examples/schema.rs +++ b/contracts/external/dao-migrator/examples/schema.rs @@ -1,11 +1,10 @@ use cosmwasm_schema::write_api; -use dao_proposal_single::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use dao_migrator::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { instantiate: InstantiateMsg, query: QueryMsg, execute: ExecuteMsg, - migrate: MigrateMsg, } } diff --git a/contracts/external/dao-migrator/schema/dao-migrator.json b/contracts/external/dao-migrator/schema/dao-migrator.json index bd1045e43..5a3030f4e 100644 --- a/contracts/external/dao-migrator/schema/dao-migrator.json +++ b/contracts/external/dao-migrator/schema/dao-migrator.json @@ -7,60 +7,26 @@ "title": "InstantiateMsg", "type": "object", "required": [ - "allow_revoting", - "close_proposal_on_execution_failure", - "max_voting_period", - "only_members_execute", - "pre_propose_info", - "threshold" + "migration_params", + "sub_daos", + "v1_code_ids", + "v2_code_ids" ], "properties": { - "allow_revoting": { - "description": "Allows changing votes before the proposal expires. If this is enabled proposals will not be able to complete early as final vote information is not known until the time of proposal expiration.", - "type": "boolean" + "migration_params": { + "$ref": "#/definitions/MigrationParams" }, - "close_proposal_on_execution_failure": { - "description": "If set to true proposals will be closed if their execution fails. Otherwise, proposals will remain open after execution failure. For example, with this enabled a proposal to send 5 tokens out of a DAO's treasury with 4 tokens would be closed when it is executed. With this disabled, that same proposal would remain open until the DAO's treasury was large enough for it to be executed.", - "type": "boolean" - }, - "max_voting_period": { - "description": "The default maximum amount of time a proposal may be voted on before expiring.", - "allOf": [ - { - "$ref": "#/definitions/Duration" - } - ] - }, - "min_voting_period": { - "description": "The minimum amount of time a proposal must be open before passing. A proposal may fail before this amount of time has elapsed, but it will not pass. This can be useful for preventing governance attacks wherein an attacker aquires a large number of tokens and forces a proposal through.", - "anyOf": [ - { - "$ref": "#/definitions/Duration" - }, - { - "type": "null" - } - ] - }, - "only_members_execute": { - "description": "If set to true only members may execute passed proposals. Otherwise, any address may execute a passed proposal.", - "type": "boolean" + "sub_daos": { + "type": "array", + "items": { + "$ref": "#/definitions/SubDao" + } }, - "pre_propose_info": { - "description": "Information about what addresses may create proposals.", - "allOf": [ - { - "$ref": "#/definitions/PreProposeInfo" - } - ] + "v1_code_ids": { + "$ref": "#/definitions/V1CodeIds" }, - "threshold": { - "description": "The threshold a proposal must reach to complete.", - "allOf": [ - { - "$ref": "#/definitions/Threshold" - } - ] + "v2_code_ids": { + "$ref": "#/definitions/V2CodeIds" } }, "additionalProperties": false, @@ -125,10 +91,6 @@ } } }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, "Duration": { "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", "oneOf": [ @@ -163,6 +125,39 @@ } ] }, + "MigrationParams": { + "type": "object", + "required": [ + "proposal_params" + ], + "properties": { + "migrate_stake_cw20_manager": { + "description": "Rather or not to migrate the stake_cw20 contract and its manager. If this is not set to true and a stake_cw20 contract is detected in the DAO's configuration the migration will be aborted.", + "type": [ + "boolean", + "null" + ] + }, + "proposal_params": { + "description": "List of (address, ProposalParams) where `address` is an address of a proposal module currently part of the DAO.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/ProposalParams" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + }, + "additionalProperties": false + }, "ModuleInstantiateInfo": { "description": "Information needed to instantiate a module.", "type": "object", @@ -212,38 +207,6 @@ }, "additionalProperties": false }, - "PercentageThreshold": { - "description": "A percentage of voting power that must vote yes for a proposal to pass. An example of why this is needed:\n\nIf a user specifies a 60% passing threshold, and there are 10 voters they likely expect that proposal to pass when there are 6 yes votes. This implies that the condition for passing should be `vote_weights >= total_votes * threshold`.\n\nWith this in mind, how should a user specify that they would like proposals to pass if the majority of voters choose yes? Selecting a 50% passing threshold with those rules doesn't properly cover that case as 5 voters voting yes out of 10 would pass the proposal. Selecting 50.0001% or or some variation of that also does not work as a very small yes vote which technically makes the majority yes may not reach that threshold.\n\nTo handle these cases we provide both a majority and percent option for all percentages. If majority is selected passing will be determined by `yes > total_votes * 0.5`. If percent is selected passing is determined by `yes >= total_votes * percent`.\n\nIn both of these cases a proposal with only abstain votes must fail. This requires a special case passing logic.", - "oneOf": [ - { - "description": "The majority of voters must vote yes for the proposal to pass.", - "type": "object", - "required": [ - "majority" - ], - "properties": { - "majority": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "A percentage of voting power >= percent must vote yes for the proposal to pass.", - "type": "object", - "required": [ - "percent" - ], - "properties": { - "percent": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - } - ] - }, "PreProposeInfo": { "oneOf": [ { @@ -284,50 +247,202 @@ } ] }, - "Threshold": { - "description": "The ways a proposal may reach its passing / failing threshold.", - "oneOf": [ - { - "description": "Declares a percentage of the total weight that must cast Yes votes in order for a proposal to pass. See `ThresholdResponse::AbsolutePercentage` in the cw3 spec for details.", - "type": "object", - "required": [ - "absolute_percentage" - ], - "properties": { - "absolute_percentage": { - "type": "object", - "required": [ - "percentage" - ], - "properties": { - "percentage": { - "$ref": "#/definitions/PercentageThreshold" - } - }, - "additionalProperties": false + "ProposalParams": { + "description": "The params we need to provide for migration msgs", + "type": "object", + "required": [ + "close_proposal_on_execution_failure", + "pre_propose_info" + ], + "properties": { + "close_proposal_on_execution_failure": { + "type": "boolean" + }, + "pre_propose_info": { + "$ref": "#/definitions/PreProposeInfo" + }, + "veto": { + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" } - }, - "additionalProperties": false + ] + } + }, + "additionalProperties": false + }, + "SubDao": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "description": "The contract address of the SubDAO", + "type": "string" + }, + "charter": { + "description": "The purpose/constitution for the SubDAO", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "V1CodeIds": { + "type": "object", + "required": [ + "cw20_stake", + "cw20_staked_balances_voting", + "cw4_voting", + "proposal_single" + ], + "properties": { + "cw20_stake": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "cw20_staked_balances_voting": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "cw4_voting": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "proposal_single": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "V2CodeIds": { + "type": "object", + "required": [ + "cw20_stake", + "cw20_staked_balances_voting", + "cw4_voting", + "proposal_single" + ], + "properties": { + "cw20_stake": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "cw20_staked_balances_voting": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "cw4_voting": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "proposal_single": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to lock a proposal for after its expiration to allow the vetoer to veto.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "type": "object", + "required": [ + "migration_params", + "sub_daos", + "v1_code_ids", + "v2_code_ids" + ], + "properties": { + "migration_params": { + "$ref": "#/definitions/MigrationParams" + }, + "sub_daos": { + "type": "array", + "items": { + "$ref": "#/definitions/SubDao" + } + }, + "v1_code_ids": { + "$ref": "#/definitions/V1CodeIds" + }, + "v2_code_ids": { + "$ref": "#/definitions/V2CodeIds" + } + }, + "additionalProperties": false, + "definitions": { + "Admin": { + "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", + "oneOf": [ { - "description": "Declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. See `ThresholdResponse::ThresholdQuorum` in the cw3 spec for details.", + "description": "Set the admin to a specified address.", "type": "object", "required": [ - "threshold_quorum" + "address" ], "properties": { - "threshold_quorum": { + "address": { "type": "object", "required": [ - "quorum", - "threshold" + "addr" ], "properties": { - "quorum": { - "$ref": "#/definitions/PercentageThreshold" - }, - "threshold": { - "$ref": "#/definitions/PercentageThreshold" + "addr": { + "type": "string" } }, "additionalProperties": false @@ -336,22 +451,14 @@ "additionalProperties": false }, { - "description": "An absolute number of votes needed for something to cross the threshold. Useful for multisig style voting.", + "description": "Sets the admin as the core module address.", "type": "object", "required": [ - "absolute_count" + "core_module" ], "properties": { - "absolute_count": { + "core_module": { "type": "object", - "required": [ - "threshold" - ], - "properties": { - "threshold": { - "$ref": "#/definitions/Uint128" - } - }, "additionalProperties": false } }, @@ -359,5621 +466,337 @@ } ] }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", "type": "string" - } - } - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "description": "Creates a proposal in the module.", + }, + "Coin": { "type": "object", "required": [ - "propose" + "amount", + "denom" ], "properties": { - "propose": { - "$ref": "#/definitions/SingleChoiceProposeMsg" + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" } - }, - "additionalProperties": false + } }, - { - "description": "Votes on a proposal. Voting power is determined by the DAO's voting power module.", - "type": "object", - "required": [ - "vote" - ], - "properties": { - "vote": { + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { "type": "object", "required": [ - "proposal_id", - "vote" + "height" ], "properties": { - "proposal_id": { - "description": "The ID of the proposal to vote on.", + "height": { "type": "integer", "format": "uint64", "minimum": 0.0 - }, - "rationale": { - "description": "An optional rationale for why this vote was cast. This can be updated, set, or removed later by the address casting the vote.", - "type": [ - "string", - "null" - ] - }, - "vote": { - "description": "The senders position on the proposal.", - "allOf": [ - { - "$ref": "#/definitions/Vote" - } - ] } }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Updates the sender's rationale for their vote on the specified proposal. Errors if no vote vote has been cast.", - "type": "object", - "required": [ - "update_rationale" - ], - "properties": { - "update_rationale": { + }, + { + "description": "Time in seconds", "type": "object", "required": [ - "proposal_id" + "time" ], "properties": { - "proposal_id": { + "time": { "type": "integer", "format": "uint64", "minimum": 0.0 - }, - "rationale": { - "type": [ - "string", - "null" - ] } }, "additionalProperties": false } - }, - "additionalProperties": false + ] }, - { - "description": "Causes the messages associated with a passed proposal to be executed by the DAO.", + "MigrationParams": { "type": "object", "required": [ - "execute" + "proposal_params" ], "properties": { - "execute": { - "type": "object", - "required": [ - "proposal_id" - ], - "properties": { - "proposal_id": { - "description": "The ID of the proposal to execute.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false + "migrate_stake_cw20_manager": { + "description": "Rather or not to migrate the stake_cw20 contract and its manager. If this is not set to true and a stake_cw20 contract is detected in the DAO's configuration the migration will be aborted.", + "type": [ + "boolean", + "null" + ] + }, + "proposal_params": { + "description": "List of (address, ProposalParams) where `address` is an address of a proposal module currently part of the DAO.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/ProposalParams" + } + ], + "maxItems": 2, + "minItems": 2 + } } }, "additionalProperties": false }, - { - "description": "Closes a proposal that has failed (either not passed or timed out). If applicable this will cause the proposal deposit associated wth said proposal to be returned.", + "ModuleInstantiateInfo": { + "description": "Information needed to instantiate a module.", "type": "object", "required": [ - "close" + "code_id", + "funds", + "label", + "msg" ], "properties": { - "close": { + "admin": { + "description": "CosmWasm level admin of the instantiated contract. See: ", + "anyOf": [ + { + "$ref": "#/definitions/Admin" + }, + { + "type": "null" + } + ] + }, + "code_id": { + "description": "Code ID of the contract to be instantiated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "description": "Funds to be sent to the instantiated contract.", + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "Label for the instantiated contract.", + "type": "string" + }, + "msg": { + "description": "Instantiate message to be used to create the contract.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false + }, + "PreProposeInfo": { + "oneOf": [ + { + "description": "Anyone may create a proposal free of charge.", "type": "object", "required": [ - "proposal_id" + "anyone_may_propose" ], "properties": { - "proposal_id": { - "description": "The ID of the proposal to close.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 + "anyone_may_propose": { + "type": "object", + "additionalProperties": false } }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Updates the governance module's config.", - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { + }, + { + "description": "The module specified in INFO has exclusive rights to proposal creation.", "type": "object", "required": [ - "allow_revoting", - "close_proposal_on_execution_failure", - "dao", - "max_voting_period", - "only_members_execute", - "threshold" + "module_may_propose" ], "properties": { - "allow_revoting": { - "description": "Allows changing votes before the proposal expires. If this is enabled proposals will not be able to complete early as final vote information is not known until the time of proposal expiration.", - "type": "boolean" - }, - "close_proposal_on_execution_failure": { - "description": "If set to true proposals will be closed if their execution fails. Otherwise, proposals will remain open after execution failure. For example, with this enabled a proposal to send 5 tokens out of a DAO's treasury with 4 tokens would be closed when it is executed. With this disabled, that same proposal would remain open until the DAO's treasury was large enough for it to be executed.", - "type": "boolean" - }, - "dao": { - "description": "The address if tge DAO that this governance module is associated with.", - "type": "string" - }, - "max_voting_period": { - "description": "The default maximum amount of time a proposal may be voted on before expiring. This will only apply to proposals created after the config update.", - "allOf": [ - { - "$ref": "#/definitions/Duration" - } - ] - }, - "min_voting_period": { - "description": "The minimum amount of time a proposal must be open before passing. A proposal may fail before this amount of time has elapsed, but it will not pass. This can be useful for preventing governance attacks wherein an attacker aquires a large number of tokens and forces a proposal through.", - "anyOf": [ - { - "$ref": "#/definitions/Duration" - }, - { - "type": "null" - } - ] - }, - "only_members_execute": { - "description": "If set to true only members may execute passed proposals. Otherwise, any address may execute a passed proposal. Applies to all outstanding and future proposals.", - "type": "boolean" - }, - "threshold": { - "description": "The new proposal passing threshold. This will only apply to proposals created after the config update.", - "allOf": [ - { - "$ref": "#/definitions/Threshold" + "module_may_propose": { + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "$ref": "#/definitions/ModuleInstantiateInfo" } - ] + }, + "additionalProperties": false } }, "additionalProperties": false } - }, - "additionalProperties": false + ] }, - { - "description": "Update's the proposal creation policy used for this module. Only the DAO may call this method.", + "ProposalParams": { + "description": "The params we need to provide for migration msgs", "type": "object", "required": [ - "update_pre_propose_info" + "close_proposal_on_execution_failure", + "pre_propose_info" ], "properties": { - "update_pre_propose_info": { - "type": "object", - "required": [ - "info" - ], - "properties": { - "info": { - "$ref": "#/definitions/PreProposeInfo" + "close_proposal_on_execution_failure": { + "type": "boolean" + }, + "pre_propose_info": { + "$ref": "#/definitions/PreProposeInfo" + }, + "veto": { + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" } - }, - "additionalProperties": false + ] } }, "additionalProperties": false }, - { - "description": "Adds an address as a consumer of proposal hooks. Consumers of proposal hooks have hook messages executed on them whenever the status of a proposal changes or a proposal is created. If a consumer contract errors when handling a hook message it will be removed from the list of consumers.", + "SubDao": { "type": "object", "required": [ - "add_proposal_hook" + "addr" ], "properties": { - "add_proposal_hook": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false + "addr": { + "description": "The contract address of the SubDAO", + "type": "string" + }, + "charter": { + "description": "The purpose/constitution for the SubDAO", + "type": [ + "string", + "null" + ] } }, "additionalProperties": false }, - { - "description": "Removes a consumer of proposal hooks.", + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "V1CodeIds": { "type": "object", "required": [ - "remove_proposal_hook" + "cw20_stake", + "cw20_staked_balances_voting", + "cw4_voting", + "proposal_single" ], "properties": { - "remove_proposal_hook": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false + "cw20_stake": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "cw20_staked_balances_voting": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "cw4_voting": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "proposal_single": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 } }, "additionalProperties": false }, - { - "description": "Adds an address as a consumer of vote hooks. Consumers of vote hooks have hook messages executed on them whenever the a vote is cast. If a consumer contract errors when handling a hook message it will be removed from the list of consumers.", + "V2CodeIds": { "type": "object", "required": [ - "add_vote_hook" + "cw20_stake", + "cw20_staked_balances_voting", + "cw4_voting", + "proposal_single" ], "properties": { - "add_vote_hook": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false + "cw20_stake": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "cw20_staked_balances_voting": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "cw4_voting": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "proposal_single": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 } }, "additionalProperties": false }, - { - "description": "Removed a consumer of vote hooks.", + "VetoConfig": { "type": "object", "required": [ - "remove_vote_hook" + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" ], "properties": { - "remove_vote_hook": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to lock a proposal for after its expiration to allow the vetoer to veto.", + "allOf": [ + { + "$ref": "#/definitions/Duration" } - }, - "additionalProperties": false + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" } }, "additionalProperties": false } - ], - "definitions": { - "Admin": { - "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", - "oneOf": [ - { - "description": "Set the admin to a specified address.", - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "object", - "required": [ - "addr" - ], - "properties": { - "addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Sets the admin as the core module address.", - "type": "object", - "required": [ - "core_module" - ], - "properties": { - "core_module": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "BankMsg": { - "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", - "oneOf": [ - { - "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "send" - ], - "properties": { - "send": { - "type": "object", - "required": [ - "amount", - "to_address" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "to_address": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "CosmosMsg_for_Empty": { - "oneOf": [ - { - "type": "object", - "required": [ - "bank" - ], - "properties": { - "bank": { - "$ref": "#/definitions/BankMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "custom" - ], - "properties": { - "custom": { - "$ref": "#/definitions/Empty" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "staking" - ], - "properties": { - "staking": { - "$ref": "#/definitions/StakingMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "distribution" - ], - "properties": { - "distribution": { - "$ref": "#/definitions/DistributionMsg" - } - }, - "additionalProperties": false - }, - { - "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", - "type": "object", - "required": [ - "stargate" - ], - "properties": { - "stargate": { - "type": "object", - "required": [ - "type_url", - "value" - ], - "properties": { - "type_url": { - "type": "string" - }, - "value": { - "$ref": "#/definitions/Binary" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "ibc" - ], - "properties": { - "ibc": { - "$ref": "#/definitions/IbcMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "wasm" - ], - "properties": { - "wasm": { - "$ref": "#/definitions/WasmMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "gov" - ], - "properties": { - "gov": { - "$ref": "#/definitions/GovMsg" - } - }, - "additionalProperties": false - } - ] - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "DistributionMsg": { - "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", - "oneOf": [ - { - "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "set_withdraw_address" - ], - "properties": { - "set_withdraw_address": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "description": "The `withdraw_address`", - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "withdraw_delegator_reward" - ], - "properties": { - "withdraw_delegator_reward": { - "type": "object", - "required": [ - "validator" - ], - "properties": { - "validator": { - "description": "The `validator_address`", - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Duration": { - "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", - "oneOf": [ - { - "type": "object", - "required": [ - "height" - ], - "properties": { - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "Time in seconds", - "type": "object", - "required": [ - "time" - ], - "properties": { - "time": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - ] - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, - "GovMsg": { - "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", - "oneOf": [ - { - "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", - "type": "object", - "required": [ - "vote" - ], - "properties": { - "vote": { - "type": "object", - "required": [ - "proposal_id", - "vote" - ], - "properties": { - "proposal_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "vote": { - "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", - "allOf": [ - { - "$ref": "#/definitions/VoteOption" - } - ] - } - } - } - }, - "additionalProperties": false - } - ] - }, - "IbcMsg": { - "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", - "oneOf": [ - { - "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", - "type": "object", - "required": [ - "transfer" - ], - "properties": { - "transfer": { - "type": "object", - "required": [ - "amount", - "channel_id", - "timeout", - "to_address" - ], - "properties": { - "amount": { - "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", - "allOf": [ - { - "$ref": "#/definitions/Coin" - } - ] - }, - "channel_id": { - "description": "existing channel to send the tokens over", - "type": "string" - }, - "timeout": { - "description": "when packet times out, measured on remote chain", - "allOf": [ - { - "$ref": "#/definitions/IbcTimeout" - } - ] - }, - "to_address": { - "description": "address on the remote chain to receive these tokens", - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", - "type": "object", - "required": [ - "send_packet" - ], - "properties": { - "send_packet": { - "type": "object", - "required": [ - "channel_id", - "data", - "timeout" - ], - "properties": { - "channel_id": { - "type": "string" - }, - "data": { - "$ref": "#/definitions/Binary" - }, - "timeout": { - "description": "when packet times out, measured on remote chain", - "allOf": [ - { - "$ref": "#/definitions/IbcTimeout" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", - "type": "object", - "required": [ - "close_channel" - ], - "properties": { - "close_channel": { - "type": "object", - "required": [ - "channel_id" - ], - "properties": { - "channel_id": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "IbcTimeout": { - "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", - "type": "object", - "properties": { - "block": { - "anyOf": [ - { - "$ref": "#/definitions/IbcTimeoutBlock" - }, - { - "type": "null" - } - ] - }, - "timestamp": { - "anyOf": [ - { - "$ref": "#/definitions/Timestamp" - }, - { - "type": "null" - } - ] - } - } - }, - "IbcTimeoutBlock": { - "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", - "type": "object", - "required": [ - "height", - "revision" - ], - "properties": { - "height": { - "description": "block height after which the packet times out. the height within the given revision", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "revision": { - "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - }, - "ModuleInstantiateInfo": { - "description": "Information needed to instantiate a module.", - "type": "object", - "required": [ - "code_id", - "funds", - "label", - "msg" - ], - "properties": { - "admin": { - "description": "CosmWasm level admin of the instantiated contract. See: ", - "anyOf": [ - { - "$ref": "#/definitions/Admin" - }, - { - "type": "null" - } - ] - }, - "code_id": { - "description": "Code ID of the contract to be instantiated.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "funds": { - "description": "Funds to be sent to the instantiated contract.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "label": { - "description": "Label for the instantiated contract.", - "type": "string" - }, - "msg": { - "description": "Instantiate message to be used to create the contract.", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - }, - "additionalProperties": false - }, - "PercentageThreshold": { - "description": "A percentage of voting power that must vote yes for a proposal to pass. An example of why this is needed:\n\nIf a user specifies a 60% passing threshold, and there are 10 voters they likely expect that proposal to pass when there are 6 yes votes. This implies that the condition for passing should be `vote_weights >= total_votes * threshold`.\n\nWith this in mind, how should a user specify that they would like proposals to pass if the majority of voters choose yes? Selecting a 50% passing threshold with those rules doesn't properly cover that case as 5 voters voting yes out of 10 would pass the proposal. Selecting 50.0001% or or some variation of that also does not work as a very small yes vote which technically makes the majority yes may not reach that threshold.\n\nTo handle these cases we provide both a majority and percent option for all percentages. If majority is selected passing will be determined by `yes > total_votes * 0.5`. If percent is selected passing is determined by `yes >= total_votes * percent`.\n\nIn both of these cases a proposal with only abstain votes must fail. This requires a special case passing logic.", - "oneOf": [ - { - "description": "The majority of voters must vote yes for the proposal to pass.", - "type": "object", - "required": [ - "majority" - ], - "properties": { - "majority": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "A percentage of voting power >= percent must vote yes for the proposal to pass.", - "type": "object", - "required": [ - "percent" - ], - "properties": { - "percent": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - } - ] - }, - "PreProposeInfo": { - "oneOf": [ - { - "description": "Anyone may create a proposal free of charge.", - "type": "object", - "required": [ - "anyone_may_propose" - ], - "properties": { - "anyone_may_propose": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "The module specified in INFO has exclusive rights to proposal creation.", - "type": "object", - "required": [ - "module_may_propose" - ], - "properties": { - "module_may_propose": { - "type": "object", - "required": [ - "info" - ], - "properties": { - "info": { - "$ref": "#/definitions/ModuleInstantiateInfo" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "SingleChoiceProposeMsg": { - "description": "The contents of a message to create a proposal in the single choice proposal module.\n\nWe break this type out of `ExecuteMsg` because we want pre-propose modules that interact with this contract to be able to get type checking on their propose messages.\n\nWe move this type to this package so that pre-propose modules can import it without importing dao-proposal-single with the library feature which (as it is not additive) cause the execute exports to not be included in wasm builds.", - "type": "object", - "required": [ - "description", - "msgs", - "title" - ], - "properties": { - "description": { - "description": "A description of the proposal.", - "type": "string" - }, - "msgs": { - "description": "The messages that should be executed in response to this proposal passing.", - "type": "array", - "items": { - "$ref": "#/definitions/CosmosMsg_for_Empty" - } - }, - "proposer": { - "description": "The address creating the proposal. If no pre-propose module is attached to this module this must always be None as the proposer is the sender of the propose message. If a pre-propose module is attached, this must be Some and will set the proposer of the proposal it creates.", - "type": [ - "string", - "null" - ] - }, - "title": { - "description": "The title of the proposal.", - "type": "string" - } - }, - "additionalProperties": false - }, - "StakingMsg": { - "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", - "oneOf": [ - { - "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "delegate" - ], - "properties": { - "delegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "undelegate" - ], - "properties": { - "undelegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "redelegate" - ], - "properties": { - "redelegate": { - "type": "object", - "required": [ - "amount", - "dst_validator", - "src_validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "dst_validator": { - "type": "string" - }, - "src_validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Threshold": { - "description": "The ways a proposal may reach its passing / failing threshold.", - "oneOf": [ - { - "description": "Declares a percentage of the total weight that must cast Yes votes in order for a proposal to pass. See `ThresholdResponse::AbsolutePercentage` in the cw3 spec for details.", - "type": "object", - "required": [ - "absolute_percentage" - ], - "properties": { - "absolute_percentage": { - "type": "object", - "required": [ - "percentage" - ], - "properties": { - "percentage": { - "$ref": "#/definitions/PercentageThreshold" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. See `ThresholdResponse::ThresholdQuorum` in the cw3 spec for details.", - "type": "object", - "required": [ - "threshold_quorum" - ], - "properties": { - "threshold_quorum": { - "type": "object", - "required": [ - "quorum", - "threshold" - ], - "properties": { - "quorum": { - "$ref": "#/definitions/PercentageThreshold" - }, - "threshold": { - "$ref": "#/definitions/PercentageThreshold" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "An absolute number of votes needed for something to cross the threshold. Useful for multisig style voting.", - "type": "object", - "required": [ - "absolute_count" - ], - "properties": { - "absolute_count": { - "type": "object", - "required": [ - "threshold" - ], - "properties": { - "threshold": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - }, - "Vote": { - "oneOf": [ - { - "description": "Marks support for the proposal.", - "type": "string", - "enum": [ - "yes" - ] - }, - { - "description": "Marks opposition to the proposal.", - "type": "string", - "enum": [ - "no" - ] - }, - { - "description": "Marks participation but does not count towards the ratio of support / opposed.", - "type": "string", - "enum": [ - "abstain" - ] - } - ] - }, - "VoteOption": { - "type": "string", - "enum": [ - "yes", - "no", - "abstain", - "no_with_veto" - ] - }, - "WasmMsg": { - "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", - "oneOf": [ - { - "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "execute" - ], - "properties": { - "execute": { - "type": "object", - "required": [ - "contract_addr", - "funds", - "msg" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "msg": { - "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "instantiate" - ], - "properties": { - "instantiate": { - "type": "object", - "required": [ - "code_id", - "funds", - "label", - "msg" - ], - "properties": { - "admin": { - "type": [ - "string", - "null" - ] - }, - "code_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "label": { - "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", - "type": "string" - }, - "msg": { - "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "migrate" - ], - "properties": { - "migrate": { - "type": "object", - "required": [ - "contract_addr", - "msg", - "new_code_id" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "msg": { - "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - }, - "new_code_id": { - "description": "the code_id of the new logic to place in the given contract", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "update_admin" - ], - "properties": { - "update_admin": { - "type": "object", - "required": [ - "admin", - "contract_addr" - ], - "properties": { - "admin": { - "type": "string" - }, - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "clear_admin" - ], - "properties": { - "clear_admin": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - } - } - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "description": "Gets the proposal module's config.", - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Gets information about a proposal.", - "type": "object", - "required": [ - "proposal" - ], - "properties": { - "proposal": { - "type": "object", - "required": [ - "proposal_id" - ], - "properties": { - "proposal_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Lists all the proposals that have been cast in this module.", - "type": "object", - "required": [ - "list_proposals" - ], - "properties": { - "list_proposals": { - "type": "object", - "properties": { - "limit": { - "description": "The maximum number of proposals to return as part of this query. If no limit is set a max of 30 proposals will be returned.", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "start_after": { - "description": "The proposal ID to start listing proposals after. For example, if this is set to 2 proposals with IDs 3 and higher will be returned.", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Lists all of the proposals that have been cast in this module in decending order of proposal ID.", - "type": "object", - "required": [ - "reverse_proposals" - ], - "properties": { - "reverse_proposals": { - "type": "object", - "properties": { - "limit": { - "description": "The maximum number of proposals to return as part of this query. If no limit is set a max of 30 proposals will be returned.", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "start_before": { - "description": "The proposal ID to start listing proposals before. For example, if this is set to 6 proposals with IDs 5 and lower will be returned.", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns a voters position on a propsal.", - "type": "object", - "required": [ - "get_vote" - ], - "properties": { - "get_vote": { - "type": "object", - "required": [ - "proposal_id", - "voter" - ], - "properties": { - "proposal_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "voter": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Lists all of the votes that have been cast on a proposal.", - "type": "object", - "required": [ - "list_votes" - ], - "properties": { - "list_votes": { - "type": "object", - "required": [ - "proposal_id" - ], - "properties": { - "limit": { - "description": "The maximum number of votes to return in response to this query. If no limit is specified a max of 30 are returned.", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "proposal_id": { - "description": "The proposal to list the votes of.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "start_after": { - "description": "The voter to start listing votes after. Ordering is done alphabetically.", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns the number of proposals that have been created in this module.", - "type": "object", - "required": [ - "proposal_count" - ], - "properties": { - "proposal_count": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Gets the current proposal creation policy for this module.", - "type": "object", - "required": [ - "proposal_creation_policy" - ], - "properties": { - "proposal_creation_policy": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Lists all of the consumers of proposal hooks for this module.", - "type": "object", - "required": [ - "proposal_hooks" - ], - "properties": { - "proposal_hooks": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Lists all of the consumers of vote hooks for this module.", - "type": "object", - "required": [ - "vote_hooks" - ], - "properties": { - "vote_hooks": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns the address of the DAO this module belongs to", - "type": "object", - "required": [ - "dao" - ], - "properties": { - "dao": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns contract version info", - "type": "object", - "required": [ - "info" - ], - "properties": { - "info": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns the proposal ID that will be assigned to the next proposal created.", - "type": "object", - "required": [ - "next_proposal_id" - ], - "properties": { - "next_proposal_id": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "migrate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MigrateMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "from_v1" - ], - "properties": { - "from_v1": { - "type": "object", - "required": [ - "close_proposal_on_execution_failure", - "pre_propose_info" - ], - "properties": { - "close_proposal_on_execution_failure": { - "description": "This field was not present in DAO DAO v1. To migrate, a value must be specified.\n\nIf set to true proposals will be closed if their execution fails. Otherwise, proposals will remain open after execution failure. For example, with this enabled a proposal to send 5 tokens out of a DAO's treasury with 4 tokens would be closed when it is executed. With this disabled, that same proposal would remain open until the DAO's treasury was large enough for it to be executed.", - "type": "boolean" - }, - "pre_propose_info": { - "description": "This field was not present in DAO DAO v1. To migrate, a value must be specified.\n\nThis contains information about how a pre-propose module may be configured. If set to \"AnyoneMayPropose\", there will be no pre-propose module and consequently, no deposit or membership checks when submitting a proposal. The \"ModuleMayPropose\" option allows for instantiating a prepropose module which will handle deposit verification and return logic.", - "allOf": [ - { - "$ref": "#/definitions/PreProposeInfo" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "from_compatible" - ], - "properties": { - "from_compatible": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Admin": { - "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", - "oneOf": [ - { - "description": "Set the admin to a specified address.", - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "object", - "required": [ - "addr" - ], - "properties": { - "addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Sets the admin as the core module address.", - "type": "object", - "required": [ - "core_module" - ], - "properties": { - "core_module": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "ModuleInstantiateInfo": { - "description": "Information needed to instantiate a module.", - "type": "object", - "required": [ - "code_id", - "funds", - "label", - "msg" - ], - "properties": { - "admin": { - "description": "CosmWasm level admin of the instantiated contract. See: ", - "anyOf": [ - { - "$ref": "#/definitions/Admin" - }, - { - "type": "null" - } - ] - }, - "code_id": { - "description": "Code ID of the contract to be instantiated.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "funds": { - "description": "Funds to be sent to the instantiated contract.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "label": { - "description": "Label for the instantiated contract.", - "type": "string" - }, - "msg": { - "description": "Instantiate message to be used to create the contract.", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - }, - "additionalProperties": false - }, - "PreProposeInfo": { - "oneOf": [ - { - "description": "Anyone may create a proposal free of charge.", - "type": "object", - "required": [ - "anyone_may_propose" - ], - "properties": { - "anyone_may_propose": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "The module specified in INFO has exclusive rights to proposal creation.", - "type": "object", - "required": [ - "module_may_propose" - ], - "properties": { - "module_may_propose": { - "type": "object", - "required": [ - "info" - ], - "properties": { - "info": { - "$ref": "#/definitions/ModuleInstantiateInfo" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "sudo": null, - "responses": { - "config": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Config", - "description": "The governance module's configuration.", - "type": "object", - "required": [ - "allow_revoting", - "close_proposal_on_execution_failure", - "dao", - "max_voting_period", - "only_members_execute", - "threshold" - ], - "properties": { - "allow_revoting": { - "description": "Allows changing votes before the proposal expires. If this is enabled proposals will not be able to complete early as final vote information is not known until the time of proposal expiration.", - "type": "boolean" - }, - "close_proposal_on_execution_failure": { - "description": "If set to true proposals will be closed if their execution fails. Otherwise, proposals will remain open after execution failure. For example, with this enabled a proposal to send 5 tokens out of a DAO's treasury with 4 tokens would be closed when it is executed. With this disabled, that same proposal would remain open until the DAO's treasury was large enough for it to be executed.", - "type": "boolean" - }, - "dao": { - "description": "The address of the DAO that this governance module is associated with.", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "max_voting_period": { - "description": "The default maximum amount of time a proposal may be voted on before expiring.", - "allOf": [ - { - "$ref": "#/definitions/Duration" - } - ] - }, - "min_voting_period": { - "description": "The minimum amount of time a proposal must be open before passing. A proposal may fail before this amount of time has elapsed, but it will not pass. This can be useful for preventing governance attacks wherein an attacker aquires a large number of tokens and forces a proposal through.", - "anyOf": [ - { - "$ref": "#/definitions/Duration" - }, - { - "type": "null" - } - ] - }, - "only_members_execute": { - "description": "If set to true only members may execute passed proposals. Otherwise, any address may execute a passed proposal.", - "type": "boolean" - }, - "threshold": { - "description": "The threshold a proposal must reach to complete.", - "allOf": [ - { - "$ref": "#/definitions/Threshold" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "Duration": { - "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", - "oneOf": [ - { - "type": "object", - "required": [ - "height" - ], - "properties": { - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "Time in seconds", - "type": "object", - "required": [ - "time" - ], - "properties": { - "time": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - ] - }, - "PercentageThreshold": { - "description": "A percentage of voting power that must vote yes for a proposal to pass. An example of why this is needed:\n\nIf a user specifies a 60% passing threshold, and there are 10 voters they likely expect that proposal to pass when there are 6 yes votes. This implies that the condition for passing should be `vote_weights >= total_votes * threshold`.\n\nWith this in mind, how should a user specify that they would like proposals to pass if the majority of voters choose yes? Selecting a 50% passing threshold with those rules doesn't properly cover that case as 5 voters voting yes out of 10 would pass the proposal. Selecting 50.0001% or or some variation of that also does not work as a very small yes vote which technically makes the majority yes may not reach that threshold.\n\nTo handle these cases we provide both a majority and percent option for all percentages. If majority is selected passing will be determined by `yes > total_votes * 0.5`. If percent is selected passing is determined by `yes >= total_votes * percent`.\n\nIn both of these cases a proposal with only abstain votes must fail. This requires a special case passing logic.", - "oneOf": [ - { - "description": "The majority of voters must vote yes for the proposal to pass.", - "type": "object", - "required": [ - "majority" - ], - "properties": { - "majority": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "A percentage of voting power >= percent must vote yes for the proposal to pass.", - "type": "object", - "required": [ - "percent" - ], - "properties": { - "percent": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - } - ] - }, - "Threshold": { - "description": "The ways a proposal may reach its passing / failing threshold.", - "oneOf": [ - { - "description": "Declares a percentage of the total weight that must cast Yes votes in order for a proposal to pass. See `ThresholdResponse::AbsolutePercentage` in the cw3 spec for details.", - "type": "object", - "required": [ - "absolute_percentage" - ], - "properties": { - "absolute_percentage": { - "type": "object", - "required": [ - "percentage" - ], - "properties": { - "percentage": { - "$ref": "#/definitions/PercentageThreshold" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. See `ThresholdResponse::ThresholdQuorum` in the cw3 spec for details.", - "type": "object", - "required": [ - "threshold_quorum" - ], - "properties": { - "threshold_quorum": { - "type": "object", - "required": [ - "quorum", - "threshold" - ], - "properties": { - "quorum": { - "$ref": "#/definitions/PercentageThreshold" - }, - "threshold": { - "$ref": "#/definitions/PercentageThreshold" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "An absolute number of votes needed for something to cross the threshold. Useful for multisig style voting.", - "type": "object", - "required": [ - "absolute_count" - ], - "properties": { - "absolute_count": { - "type": "object", - "required": [ - "threshold" - ], - "properties": { - "threshold": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "dao": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Addr", - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "get_vote": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VoteResponse", - "description": "Information about a vote.", - "type": "object", - "properties": { - "vote": { - "description": "None if no such vote, Some otherwise.", - "anyOf": [ - { - "$ref": "#/definitions/VoteInfo" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Vote": { - "oneOf": [ - { - "description": "Marks support for the proposal.", - "type": "string", - "enum": [ - "yes" - ] - }, - { - "description": "Marks opposition to the proposal.", - "type": "string", - "enum": [ - "no" - ] - }, - { - "description": "Marks participation but does not count towards the ratio of support / opposed.", - "type": "string", - "enum": [ - "abstain" - ] - } - ] - }, - "VoteInfo": { - "description": "Information about a vote that was cast.", - "type": "object", - "required": [ - "power", - "vote", - "voter" - ], - "properties": { - "power": { - "description": "The voting power behind the vote.", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "rationale": { - "description": "Address-specified rationale for the vote.", - "type": [ - "string", - "null" - ] - }, - "vote": { - "description": "Position on the vote.", - "allOf": [ - { - "$ref": "#/definitions/Vote" - } - ] - }, - "voter": { - "description": "The address that voted.", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - } - }, - "additionalProperties": false - } - } - }, - "info": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InfoResponse", - "type": "object", - "required": [ - "info" - ], - "properties": { - "info": { - "$ref": "#/definitions/ContractVersion" - } - }, - "additionalProperties": false, - "definitions": { - "ContractVersion": { - "type": "object", - "required": [ - "contract", - "version" - ], - "properties": { - "contract": { - "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", - "type": "string" - }, - "version": { - "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", - "type": "string" - } - }, - "additionalProperties": false - } - } - }, - "list_proposals": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ProposalListResponse", - "description": "A list of proposals returned by `ListProposals` and `ReverseProposals`.", - "type": "object", - "required": [ - "proposals" - ], - "properties": { - "proposals": { - "type": "array", - "items": { - "$ref": "#/definitions/ProposalResponse" - } - } - }, - "additionalProperties": false, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "BankMsg": { - "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", - "oneOf": [ - { - "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "send" - ], - "properties": { - "send": { - "type": "object", - "required": [ - "amount", - "to_address" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "to_address": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "CosmosMsg_for_Empty": { - "oneOf": [ - { - "type": "object", - "required": [ - "bank" - ], - "properties": { - "bank": { - "$ref": "#/definitions/BankMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "custom" - ], - "properties": { - "custom": { - "$ref": "#/definitions/Empty" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "staking" - ], - "properties": { - "staking": { - "$ref": "#/definitions/StakingMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "distribution" - ], - "properties": { - "distribution": { - "$ref": "#/definitions/DistributionMsg" - } - }, - "additionalProperties": false - }, - { - "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", - "type": "object", - "required": [ - "stargate" - ], - "properties": { - "stargate": { - "type": "object", - "required": [ - "type_url", - "value" - ], - "properties": { - "type_url": { - "type": "string" - }, - "value": { - "$ref": "#/definitions/Binary" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "ibc" - ], - "properties": { - "ibc": { - "$ref": "#/definitions/IbcMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "wasm" - ], - "properties": { - "wasm": { - "$ref": "#/definitions/WasmMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "gov" - ], - "properties": { - "gov": { - "$ref": "#/definitions/GovMsg" - } - }, - "additionalProperties": false - } - ] - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "DistributionMsg": { - "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", - "oneOf": [ - { - "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "set_withdraw_address" - ], - "properties": { - "set_withdraw_address": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "description": "The `withdraw_address`", - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "withdraw_delegator_reward" - ], - "properties": { - "withdraw_delegator_reward": { - "type": "object", - "required": [ - "validator" - ], - "properties": { - "validator": { - "description": "The `validator_address`", - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "GovMsg": { - "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", - "oneOf": [ - { - "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", - "type": "object", - "required": [ - "vote" - ], - "properties": { - "vote": { - "type": "object", - "required": [ - "proposal_id", - "vote" - ], - "properties": { - "proposal_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "vote": { - "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", - "allOf": [ - { - "$ref": "#/definitions/VoteOption" - } - ] - } - } - } - }, - "additionalProperties": false - } - ] - }, - "IbcMsg": { - "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", - "oneOf": [ - { - "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", - "type": "object", - "required": [ - "transfer" - ], - "properties": { - "transfer": { - "type": "object", - "required": [ - "amount", - "channel_id", - "timeout", - "to_address" - ], - "properties": { - "amount": { - "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", - "allOf": [ - { - "$ref": "#/definitions/Coin" - } - ] - }, - "channel_id": { - "description": "existing channel to send the tokens over", - "type": "string" - }, - "timeout": { - "description": "when packet times out, measured on remote chain", - "allOf": [ - { - "$ref": "#/definitions/IbcTimeout" - } - ] - }, - "to_address": { - "description": "address on the remote chain to receive these tokens", - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", - "type": "object", - "required": [ - "send_packet" - ], - "properties": { - "send_packet": { - "type": "object", - "required": [ - "channel_id", - "data", - "timeout" - ], - "properties": { - "channel_id": { - "type": "string" - }, - "data": { - "$ref": "#/definitions/Binary" - }, - "timeout": { - "description": "when packet times out, measured on remote chain", - "allOf": [ - { - "$ref": "#/definitions/IbcTimeout" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", - "type": "object", - "required": [ - "close_channel" - ], - "properties": { - "close_channel": { - "type": "object", - "required": [ - "channel_id" - ], - "properties": { - "channel_id": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "IbcTimeout": { - "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", - "type": "object", - "properties": { - "block": { - "anyOf": [ - { - "$ref": "#/definitions/IbcTimeoutBlock" - }, - { - "type": "null" - } - ] - }, - "timestamp": { - "anyOf": [ - { - "$ref": "#/definitions/Timestamp" - }, - { - "type": "null" - } - ] - } - } - }, - "IbcTimeoutBlock": { - "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", - "type": "object", - "required": [ - "height", - "revision" - ], - "properties": { - "height": { - "description": "block height after which the packet times out. the height within the given revision", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "revision": { - "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - }, - "PercentageThreshold": { - "description": "A percentage of voting power that must vote yes for a proposal to pass. An example of why this is needed:\n\nIf a user specifies a 60% passing threshold, and there are 10 voters they likely expect that proposal to pass when there are 6 yes votes. This implies that the condition for passing should be `vote_weights >= total_votes * threshold`.\n\nWith this in mind, how should a user specify that they would like proposals to pass if the majority of voters choose yes? Selecting a 50% passing threshold with those rules doesn't properly cover that case as 5 voters voting yes out of 10 would pass the proposal. Selecting 50.0001% or or some variation of that also does not work as a very small yes vote which technically makes the majority yes may not reach that threshold.\n\nTo handle these cases we provide both a majority and percent option for all percentages. If majority is selected passing will be determined by `yes > total_votes * 0.5`. If percent is selected passing is determined by `yes >= total_votes * percent`.\n\nIn both of these cases a proposal with only abstain votes must fail. This requires a special case passing logic.", - "oneOf": [ - { - "description": "The majority of voters must vote yes for the proposal to pass.", - "type": "object", - "required": [ - "majority" - ], - "properties": { - "majority": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "A percentage of voting power >= percent must vote yes for the proposal to pass.", - "type": "object", - "required": [ - "percent" - ], - "properties": { - "percent": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - } - ] - }, - "ProposalResponse": { - "description": "Information about a proposal returned by proposal queries.", - "type": "object", - "required": [ - "id", - "proposal" - ], - "properties": { - "id": { - "description": "The ID of the proposal being returned.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "proposal": { - "$ref": "#/definitions/SingleChoiceProposal" - } - }, - "additionalProperties": false - }, - "SingleChoiceProposal": { - "type": "object", - "required": [ - "allow_revoting", - "description", - "expiration", - "msgs", - "proposer", - "start_height", - "status", - "threshold", - "title", - "total_power", - "votes" - ], - "properties": { - "allow_revoting": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "expiration": { - "description": "The the time at which this proposal will expire and close for additional votes.", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "min_voting_period": { - "description": "The minimum amount of time this proposal must remain open for voting. The proposal may not pass unless this is expired or None.", - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "msgs": { - "description": "The messages that will be executed should this proposal pass.", - "type": "array", - "items": { - "$ref": "#/definitions/CosmosMsg_for_Empty" - } - }, - "proposer": { - "description": "The address that created this proposal.", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "start_height": { - "description": "The block height at which this proposal was created. Voting power queries should query for voting power at this block height.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "status": { - "$ref": "#/definitions/Status" - }, - "threshold": { - "description": "The threshold at which this proposal will pass.", - "allOf": [ - { - "$ref": "#/definitions/Threshold" - } - ] - }, - "title": { - "type": "string" - }, - "total_power": { - "description": "The total amount of voting power at the time of this proposal's creation.", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "votes": { - "$ref": "#/definitions/Votes" - } - }, - "additionalProperties": false - }, - "StakingMsg": { - "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", - "oneOf": [ - { - "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "delegate" - ], - "properties": { - "delegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "undelegate" - ], - "properties": { - "undelegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "redelegate" - ], - "properties": { - "redelegate": { - "type": "object", - "required": [ - "amount", - "dst_validator", - "src_validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "dst_validator": { - "type": "string" - }, - "src_validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Status": { - "oneOf": [ - { - "description": "The proposal is open for voting.", - "type": "string", - "enum": [ - "open" - ] - }, - { - "description": "The proposal has been rejected.", - "type": "string", - "enum": [ - "rejected" - ] - }, - { - "description": "The proposal has been passed but has not been executed.", - "type": "string", - "enum": [ - "passed" - ] - }, - { - "description": "The proposal has been passed and executed.", - "type": "string", - "enum": [ - "executed" - ] - }, - { - "description": "The proposal has failed or expired and has been closed. A proposal deposit refund has been issued if applicable.", - "type": "string", - "enum": [ - "closed" - ] - }, - { - "description": "The proposal's execution failed.", - "type": "string", - "enum": [ - "execution_failed" - ] - } - ] - }, - "Threshold": { - "description": "The ways a proposal may reach its passing / failing threshold.", - "oneOf": [ - { - "description": "Declares a percentage of the total weight that must cast Yes votes in order for a proposal to pass. See `ThresholdResponse::AbsolutePercentage` in the cw3 spec for details.", - "type": "object", - "required": [ - "absolute_percentage" - ], - "properties": { - "absolute_percentage": { - "type": "object", - "required": [ - "percentage" - ], - "properties": { - "percentage": { - "$ref": "#/definitions/PercentageThreshold" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. See `ThresholdResponse::ThresholdQuorum` in the cw3 spec for details.", - "type": "object", - "required": [ - "threshold_quorum" - ], - "properties": { - "threshold_quorum": { - "type": "object", - "required": [ - "quorum", - "threshold" - ], - "properties": { - "quorum": { - "$ref": "#/definitions/PercentageThreshold" - }, - "threshold": { - "$ref": "#/definitions/PercentageThreshold" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "An absolute number of votes needed for something to cross the threshold. Useful for multisig style voting.", - "type": "object", - "required": [ - "absolute_count" - ], - "properties": { - "absolute_count": { - "type": "object", - "required": [ - "threshold" - ], - "properties": { - "threshold": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - }, - "VoteOption": { - "type": "string", - "enum": [ - "yes", - "no", - "abstain", - "no_with_veto" - ] - }, - "Votes": { - "type": "object", - "required": [ - "abstain", - "no", - "yes" - ], - "properties": { - "abstain": { - "$ref": "#/definitions/Uint128" - }, - "no": { - "$ref": "#/definitions/Uint128" - }, - "yes": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - }, - "WasmMsg": { - "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", - "oneOf": [ - { - "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "execute" - ], - "properties": { - "execute": { - "type": "object", - "required": [ - "contract_addr", - "funds", - "msg" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "msg": { - "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "instantiate" - ], - "properties": { - "instantiate": { - "type": "object", - "required": [ - "code_id", - "funds", - "label", - "msg" - ], - "properties": { - "admin": { - "type": [ - "string", - "null" - ] - }, - "code_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "label": { - "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", - "type": "string" - }, - "msg": { - "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "migrate" - ], - "properties": { - "migrate": { - "type": "object", - "required": [ - "contract_addr", - "msg", - "new_code_id" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "msg": { - "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - }, - "new_code_id": { - "description": "the code_id of the new logic to place in the given contract", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "update_admin" - ], - "properties": { - "update_admin": { - "type": "object", - "required": [ - "admin", - "contract_addr" - ], - "properties": { - "admin": { - "type": "string" - }, - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "clear_admin" - ], - "properties": { - "clear_admin": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - } - } - }, - "list_votes": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VoteListResponse", - "description": "Information about the votes for a proposal.", - "type": "object", - "required": [ - "votes" - ], - "properties": { - "votes": { - "type": "array", - "items": { - "$ref": "#/definitions/VoteInfo" - } - } - }, - "additionalProperties": false, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Vote": { - "oneOf": [ - { - "description": "Marks support for the proposal.", - "type": "string", - "enum": [ - "yes" - ] - }, - { - "description": "Marks opposition to the proposal.", - "type": "string", - "enum": [ - "no" - ] - }, - { - "description": "Marks participation but does not count towards the ratio of support / opposed.", - "type": "string", - "enum": [ - "abstain" - ] - } - ] - }, - "VoteInfo": { - "description": "Information about a vote that was cast.", - "type": "object", - "required": [ - "power", - "vote", - "voter" - ], - "properties": { - "power": { - "description": "The voting power behind the vote.", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "rationale": { - "description": "Address-specified rationale for the vote.", - "type": [ - "string", - "null" - ] - }, - "vote": { - "description": "Position on the vote.", - "allOf": [ - { - "$ref": "#/definitions/Vote" - } - ] - }, - "voter": { - "description": "The address that voted.", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - } - }, - "additionalProperties": false - } - } - }, - "next_proposal_id": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "uint64", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "proposal": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ProposalResponse", - "description": "Information about a proposal returned by proposal queries.", - "type": "object", - "required": [ - "id", - "proposal" - ], - "properties": { - "id": { - "description": "The ID of the proposal being returned.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "proposal": { - "$ref": "#/definitions/SingleChoiceProposal" - } - }, - "additionalProperties": false, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "BankMsg": { - "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", - "oneOf": [ - { - "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "send" - ], - "properties": { - "send": { - "type": "object", - "required": [ - "amount", - "to_address" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "to_address": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "CosmosMsg_for_Empty": { - "oneOf": [ - { - "type": "object", - "required": [ - "bank" - ], - "properties": { - "bank": { - "$ref": "#/definitions/BankMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "custom" - ], - "properties": { - "custom": { - "$ref": "#/definitions/Empty" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "staking" - ], - "properties": { - "staking": { - "$ref": "#/definitions/StakingMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "distribution" - ], - "properties": { - "distribution": { - "$ref": "#/definitions/DistributionMsg" - } - }, - "additionalProperties": false - }, - { - "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", - "type": "object", - "required": [ - "stargate" - ], - "properties": { - "stargate": { - "type": "object", - "required": [ - "type_url", - "value" - ], - "properties": { - "type_url": { - "type": "string" - }, - "value": { - "$ref": "#/definitions/Binary" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "ibc" - ], - "properties": { - "ibc": { - "$ref": "#/definitions/IbcMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "wasm" - ], - "properties": { - "wasm": { - "$ref": "#/definitions/WasmMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "gov" - ], - "properties": { - "gov": { - "$ref": "#/definitions/GovMsg" - } - }, - "additionalProperties": false - } - ] - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "DistributionMsg": { - "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", - "oneOf": [ - { - "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "set_withdraw_address" - ], - "properties": { - "set_withdraw_address": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "description": "The `withdraw_address`", - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "withdraw_delegator_reward" - ], - "properties": { - "withdraw_delegator_reward": { - "type": "object", - "required": [ - "validator" - ], - "properties": { - "validator": { - "description": "The `validator_address`", - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "GovMsg": { - "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", - "oneOf": [ - { - "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", - "type": "object", - "required": [ - "vote" - ], - "properties": { - "vote": { - "type": "object", - "required": [ - "proposal_id", - "vote" - ], - "properties": { - "proposal_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "vote": { - "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", - "allOf": [ - { - "$ref": "#/definitions/VoteOption" - } - ] - } - } - } - }, - "additionalProperties": false - } - ] - }, - "IbcMsg": { - "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", - "oneOf": [ - { - "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", - "type": "object", - "required": [ - "transfer" - ], - "properties": { - "transfer": { - "type": "object", - "required": [ - "amount", - "channel_id", - "timeout", - "to_address" - ], - "properties": { - "amount": { - "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", - "allOf": [ - { - "$ref": "#/definitions/Coin" - } - ] - }, - "channel_id": { - "description": "existing channel to send the tokens over", - "type": "string" - }, - "timeout": { - "description": "when packet times out, measured on remote chain", - "allOf": [ - { - "$ref": "#/definitions/IbcTimeout" - } - ] - }, - "to_address": { - "description": "address on the remote chain to receive these tokens", - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", - "type": "object", - "required": [ - "send_packet" - ], - "properties": { - "send_packet": { - "type": "object", - "required": [ - "channel_id", - "data", - "timeout" - ], - "properties": { - "channel_id": { - "type": "string" - }, - "data": { - "$ref": "#/definitions/Binary" - }, - "timeout": { - "description": "when packet times out, measured on remote chain", - "allOf": [ - { - "$ref": "#/definitions/IbcTimeout" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", - "type": "object", - "required": [ - "close_channel" - ], - "properties": { - "close_channel": { - "type": "object", - "required": [ - "channel_id" - ], - "properties": { - "channel_id": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "IbcTimeout": { - "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", - "type": "object", - "properties": { - "block": { - "anyOf": [ - { - "$ref": "#/definitions/IbcTimeoutBlock" - }, - { - "type": "null" - } - ] - }, - "timestamp": { - "anyOf": [ - { - "$ref": "#/definitions/Timestamp" - }, - { - "type": "null" - } - ] - } - } - }, - "IbcTimeoutBlock": { - "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", - "type": "object", - "required": [ - "height", - "revision" - ], - "properties": { - "height": { - "description": "block height after which the packet times out. the height within the given revision", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "revision": { - "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - }, - "PercentageThreshold": { - "description": "A percentage of voting power that must vote yes for a proposal to pass. An example of why this is needed:\n\nIf a user specifies a 60% passing threshold, and there are 10 voters they likely expect that proposal to pass when there are 6 yes votes. This implies that the condition for passing should be `vote_weights >= total_votes * threshold`.\n\nWith this in mind, how should a user specify that they would like proposals to pass if the majority of voters choose yes? Selecting a 50% passing threshold with those rules doesn't properly cover that case as 5 voters voting yes out of 10 would pass the proposal. Selecting 50.0001% or or some variation of that also does not work as a very small yes vote which technically makes the majority yes may not reach that threshold.\n\nTo handle these cases we provide both a majority and percent option for all percentages. If majority is selected passing will be determined by `yes > total_votes * 0.5`. If percent is selected passing is determined by `yes >= total_votes * percent`.\n\nIn both of these cases a proposal with only abstain votes must fail. This requires a special case passing logic.", - "oneOf": [ - { - "description": "The majority of voters must vote yes for the proposal to pass.", - "type": "object", - "required": [ - "majority" - ], - "properties": { - "majority": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "A percentage of voting power >= percent must vote yes for the proposal to pass.", - "type": "object", - "required": [ - "percent" - ], - "properties": { - "percent": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - } - ] - }, - "SingleChoiceProposal": { - "type": "object", - "required": [ - "allow_revoting", - "description", - "expiration", - "msgs", - "proposer", - "start_height", - "status", - "threshold", - "title", - "total_power", - "votes" - ], - "properties": { - "allow_revoting": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "expiration": { - "description": "The the time at which this proposal will expire and close for additional votes.", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "min_voting_period": { - "description": "The minimum amount of time this proposal must remain open for voting. The proposal may not pass unless this is expired or None.", - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "msgs": { - "description": "The messages that will be executed should this proposal pass.", - "type": "array", - "items": { - "$ref": "#/definitions/CosmosMsg_for_Empty" - } - }, - "proposer": { - "description": "The address that created this proposal.", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "start_height": { - "description": "The block height at which this proposal was created. Voting power queries should query for voting power at this block height.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "status": { - "$ref": "#/definitions/Status" - }, - "threshold": { - "description": "The threshold at which this proposal will pass.", - "allOf": [ - { - "$ref": "#/definitions/Threshold" - } - ] - }, - "title": { - "type": "string" - }, - "total_power": { - "description": "The total amount of voting power at the time of this proposal's creation.", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "votes": { - "$ref": "#/definitions/Votes" - } - }, - "additionalProperties": false - }, - "StakingMsg": { - "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", - "oneOf": [ - { - "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "delegate" - ], - "properties": { - "delegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "undelegate" - ], - "properties": { - "undelegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "redelegate" - ], - "properties": { - "redelegate": { - "type": "object", - "required": [ - "amount", - "dst_validator", - "src_validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "dst_validator": { - "type": "string" - }, - "src_validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Status": { - "oneOf": [ - { - "description": "The proposal is open for voting.", - "type": "string", - "enum": [ - "open" - ] - }, - { - "description": "The proposal has been rejected.", - "type": "string", - "enum": [ - "rejected" - ] - }, - { - "description": "The proposal has been passed but has not been executed.", - "type": "string", - "enum": [ - "passed" - ] - }, - { - "description": "The proposal has been passed and executed.", - "type": "string", - "enum": [ - "executed" - ] - }, - { - "description": "The proposal has failed or expired and has been closed. A proposal deposit refund has been issued if applicable.", - "type": "string", - "enum": [ - "closed" - ] - }, - { - "description": "The proposal's execution failed.", - "type": "string", - "enum": [ - "execution_failed" - ] - } - ] - }, - "Threshold": { - "description": "The ways a proposal may reach its passing / failing threshold.", - "oneOf": [ - { - "description": "Declares a percentage of the total weight that must cast Yes votes in order for a proposal to pass. See `ThresholdResponse::AbsolutePercentage` in the cw3 spec for details.", - "type": "object", - "required": [ - "absolute_percentage" - ], - "properties": { - "absolute_percentage": { - "type": "object", - "required": [ - "percentage" - ], - "properties": { - "percentage": { - "$ref": "#/definitions/PercentageThreshold" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. See `ThresholdResponse::ThresholdQuorum` in the cw3 spec for details.", - "type": "object", - "required": [ - "threshold_quorum" - ], - "properties": { - "threshold_quorum": { - "type": "object", - "required": [ - "quorum", - "threshold" - ], - "properties": { - "quorum": { - "$ref": "#/definitions/PercentageThreshold" - }, - "threshold": { - "$ref": "#/definitions/PercentageThreshold" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "An absolute number of votes needed for something to cross the threshold. Useful for multisig style voting.", - "type": "object", - "required": [ - "absolute_count" - ], - "properties": { - "absolute_count": { - "type": "object", - "required": [ - "threshold" - ], - "properties": { - "threshold": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - }, - "VoteOption": { - "type": "string", - "enum": [ - "yes", - "no", - "abstain", - "no_with_veto" - ] - }, - "Votes": { - "type": "object", - "required": [ - "abstain", - "no", - "yes" - ], - "properties": { - "abstain": { - "$ref": "#/definitions/Uint128" - }, - "no": { - "$ref": "#/definitions/Uint128" - }, - "yes": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - }, - "WasmMsg": { - "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", - "oneOf": [ - { - "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "execute" - ], - "properties": { - "execute": { - "type": "object", - "required": [ - "contract_addr", - "funds", - "msg" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "msg": { - "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "instantiate" - ], - "properties": { - "instantiate": { - "type": "object", - "required": [ - "code_id", - "funds", - "label", - "msg" - ], - "properties": { - "admin": { - "type": [ - "string", - "null" - ] - }, - "code_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "label": { - "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", - "type": "string" - }, - "msg": { - "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "migrate" - ], - "properties": { - "migrate": { - "type": "object", - "required": [ - "contract_addr", - "msg", - "new_code_id" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "msg": { - "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - }, - "new_code_id": { - "description": "the code_id of the new logic to place in the given contract", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "update_admin" - ], - "properties": { - "update_admin": { - "type": "object", - "required": [ - "admin", - "contract_addr" - ], - "properties": { - "admin": { - "type": "string" - }, - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "clear_admin" - ], - "properties": { - "clear_admin": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - } - } - }, - "proposal_count": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "uint64", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "proposal_creation_policy": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ProposalCreationPolicy", - "oneOf": [ - { - "description": "Anyone may create a proposal, free of charge.", - "type": "object", - "required": [ - "anyone" - ], - "properties": { - "anyone": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Only ADDR may create proposals. It is expected that ADDR is a pre-propose module, though we only require that it is a valid address.", - "type": "object", - "required": [ - "module" - ], - "properties": { - "module": { - "type": "object", - "required": [ - "addr" - ], - "properties": { - "addr": { - "$ref": "#/definitions/Addr" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - } - } - }, - "proposal_hooks": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HooksResponse", - "type": "object", - "required": [ - "hooks" - ], - "properties": { - "hooks": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - "reverse_proposals": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ProposalListResponse", - "description": "A list of proposals returned by `ListProposals` and `ReverseProposals`.", - "type": "object", - "required": [ - "proposals" - ], - "properties": { - "proposals": { - "type": "array", - "items": { - "$ref": "#/definitions/ProposalResponse" - } - } - }, - "additionalProperties": false, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "BankMsg": { - "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", - "oneOf": [ - { - "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "send" - ], - "properties": { - "send": { - "type": "object", - "required": [ - "amount", - "to_address" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "to_address": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "CosmosMsg_for_Empty": { - "oneOf": [ - { - "type": "object", - "required": [ - "bank" - ], - "properties": { - "bank": { - "$ref": "#/definitions/BankMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "custom" - ], - "properties": { - "custom": { - "$ref": "#/definitions/Empty" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "staking" - ], - "properties": { - "staking": { - "$ref": "#/definitions/StakingMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "distribution" - ], - "properties": { - "distribution": { - "$ref": "#/definitions/DistributionMsg" - } - }, - "additionalProperties": false - }, - { - "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", - "type": "object", - "required": [ - "stargate" - ], - "properties": { - "stargate": { - "type": "object", - "required": [ - "type_url", - "value" - ], - "properties": { - "type_url": { - "type": "string" - }, - "value": { - "$ref": "#/definitions/Binary" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "ibc" - ], - "properties": { - "ibc": { - "$ref": "#/definitions/IbcMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "wasm" - ], - "properties": { - "wasm": { - "$ref": "#/definitions/WasmMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "gov" - ], - "properties": { - "gov": { - "$ref": "#/definitions/GovMsg" - } - }, - "additionalProperties": false - } - ] - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "DistributionMsg": { - "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", - "oneOf": [ - { - "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "set_withdraw_address" - ], - "properties": { - "set_withdraw_address": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "description": "The `withdraw_address`", - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "withdraw_delegator_reward" - ], - "properties": { - "withdraw_delegator_reward": { - "type": "object", - "required": [ - "validator" - ], - "properties": { - "validator": { - "description": "The `validator_address`", - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "GovMsg": { - "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", - "oneOf": [ - { - "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", - "type": "object", - "required": [ - "vote" - ], - "properties": { - "vote": { - "type": "object", - "required": [ - "proposal_id", - "vote" - ], - "properties": { - "proposal_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "vote": { - "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", - "allOf": [ - { - "$ref": "#/definitions/VoteOption" - } - ] - } - } - } - }, - "additionalProperties": false - } - ] - }, - "IbcMsg": { - "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", - "oneOf": [ - { - "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", - "type": "object", - "required": [ - "transfer" - ], - "properties": { - "transfer": { - "type": "object", - "required": [ - "amount", - "channel_id", - "timeout", - "to_address" - ], - "properties": { - "amount": { - "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", - "allOf": [ - { - "$ref": "#/definitions/Coin" - } - ] - }, - "channel_id": { - "description": "existing channel to send the tokens over", - "type": "string" - }, - "timeout": { - "description": "when packet times out, measured on remote chain", - "allOf": [ - { - "$ref": "#/definitions/IbcTimeout" - } - ] - }, - "to_address": { - "description": "address on the remote chain to receive these tokens", - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", - "type": "object", - "required": [ - "send_packet" - ], - "properties": { - "send_packet": { - "type": "object", - "required": [ - "channel_id", - "data", - "timeout" - ], - "properties": { - "channel_id": { - "type": "string" - }, - "data": { - "$ref": "#/definitions/Binary" - }, - "timeout": { - "description": "when packet times out, measured on remote chain", - "allOf": [ - { - "$ref": "#/definitions/IbcTimeout" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", - "type": "object", - "required": [ - "close_channel" - ], - "properties": { - "close_channel": { - "type": "object", - "required": [ - "channel_id" - ], - "properties": { - "channel_id": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "IbcTimeout": { - "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", - "type": "object", - "properties": { - "block": { - "anyOf": [ - { - "$ref": "#/definitions/IbcTimeoutBlock" - }, - { - "type": "null" - } - ] - }, - "timestamp": { - "anyOf": [ - { - "$ref": "#/definitions/Timestamp" - }, - { - "type": "null" - } - ] - } - } - }, - "IbcTimeoutBlock": { - "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", - "type": "object", - "required": [ - "height", - "revision" - ], - "properties": { - "height": { - "description": "block height after which the packet times out. the height within the given revision", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "revision": { - "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - }, - "PercentageThreshold": { - "description": "A percentage of voting power that must vote yes for a proposal to pass. An example of why this is needed:\n\nIf a user specifies a 60% passing threshold, and there are 10 voters they likely expect that proposal to pass when there are 6 yes votes. This implies that the condition for passing should be `vote_weights >= total_votes * threshold`.\n\nWith this in mind, how should a user specify that they would like proposals to pass if the majority of voters choose yes? Selecting a 50% passing threshold with those rules doesn't properly cover that case as 5 voters voting yes out of 10 would pass the proposal. Selecting 50.0001% or or some variation of that also does not work as a very small yes vote which technically makes the majority yes may not reach that threshold.\n\nTo handle these cases we provide both a majority and percent option for all percentages. If majority is selected passing will be determined by `yes > total_votes * 0.5`. If percent is selected passing is determined by `yes >= total_votes * percent`.\n\nIn both of these cases a proposal with only abstain votes must fail. This requires a special case passing logic.", - "oneOf": [ - { - "description": "The majority of voters must vote yes for the proposal to pass.", - "type": "object", - "required": [ - "majority" - ], - "properties": { - "majority": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "A percentage of voting power >= percent must vote yes for the proposal to pass.", - "type": "object", - "required": [ - "percent" - ], - "properties": { - "percent": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - } - ] - }, - "ProposalResponse": { - "description": "Information about a proposal returned by proposal queries.", - "type": "object", - "required": [ - "id", - "proposal" - ], - "properties": { - "id": { - "description": "The ID of the proposal being returned.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "proposal": { - "$ref": "#/definitions/SingleChoiceProposal" - } - }, - "additionalProperties": false - }, - "SingleChoiceProposal": { - "type": "object", - "required": [ - "allow_revoting", - "description", - "expiration", - "msgs", - "proposer", - "start_height", - "status", - "threshold", - "title", - "total_power", - "votes" - ], - "properties": { - "allow_revoting": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "expiration": { - "description": "The the time at which this proposal will expire and close for additional votes.", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "min_voting_period": { - "description": "The minimum amount of time this proposal must remain open for voting. The proposal may not pass unless this is expired or None.", - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "msgs": { - "description": "The messages that will be executed should this proposal pass.", - "type": "array", - "items": { - "$ref": "#/definitions/CosmosMsg_for_Empty" - } - }, - "proposer": { - "description": "The address that created this proposal.", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "start_height": { - "description": "The block height at which this proposal was created. Voting power queries should query for voting power at this block height.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "status": { - "$ref": "#/definitions/Status" - }, - "threshold": { - "description": "The threshold at which this proposal will pass.", - "allOf": [ - { - "$ref": "#/definitions/Threshold" - } - ] - }, - "title": { - "type": "string" - }, - "total_power": { - "description": "The total amount of voting power at the time of this proposal's creation.", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "votes": { - "$ref": "#/definitions/Votes" - } - }, - "additionalProperties": false - }, - "StakingMsg": { - "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", - "oneOf": [ - { - "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "delegate" - ], - "properties": { - "delegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "undelegate" - ], - "properties": { - "undelegate": { - "type": "object", - "required": [ - "amount", - "validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "redelegate" - ], - "properties": { - "redelegate": { - "type": "object", - "required": [ - "amount", - "dst_validator", - "src_validator" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - }, - "dst_validator": { - "type": "string" - }, - "src_validator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Status": { - "oneOf": [ - { - "description": "The proposal is open for voting.", - "type": "string", - "enum": [ - "open" - ] - }, - { - "description": "The proposal has been rejected.", - "type": "string", - "enum": [ - "rejected" - ] - }, - { - "description": "The proposal has been passed but has not been executed.", - "type": "string", - "enum": [ - "passed" - ] - }, - { - "description": "The proposal has been passed and executed.", - "type": "string", - "enum": [ - "executed" - ] - }, - { - "description": "The proposal has failed or expired and has been closed. A proposal deposit refund has been issued if applicable.", - "type": "string", - "enum": [ - "closed" - ] - }, - { - "description": "The proposal's execution failed.", - "type": "string", - "enum": [ - "execution_failed" - ] - } - ] - }, - "Threshold": { - "description": "The ways a proposal may reach its passing / failing threshold.", - "oneOf": [ - { - "description": "Declares a percentage of the total weight that must cast Yes votes in order for a proposal to pass. See `ThresholdResponse::AbsolutePercentage` in the cw3 spec for details.", - "type": "object", - "required": [ - "absolute_percentage" - ], - "properties": { - "absolute_percentage": { - "type": "object", - "required": [ - "percentage" - ], - "properties": { - "percentage": { - "$ref": "#/definitions/PercentageThreshold" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. See `ThresholdResponse::ThresholdQuorum` in the cw3 spec for details.", - "type": "object", - "required": [ - "threshold_quorum" - ], - "properties": { - "threshold_quorum": { - "type": "object", - "required": [ - "quorum", - "threshold" - ], - "properties": { - "quorum": { - "$ref": "#/definitions/PercentageThreshold" - }, - "threshold": { - "$ref": "#/definitions/PercentageThreshold" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "An absolute number of votes needed for something to cross the threshold. Useful for multisig style voting.", - "type": "object", - "required": [ - "absolute_count" - ], - "properties": { - "absolute_count": { - "type": "object", - "required": [ - "threshold" - ], - "properties": { - "threshold": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - }, - "VoteOption": { - "type": "string", - "enum": [ - "yes", - "no", - "abstain", - "no_with_veto" - ] - }, - "Votes": { - "type": "object", - "required": [ - "abstain", - "no", - "yes" - ], - "properties": { - "abstain": { - "$ref": "#/definitions/Uint128" - }, - "no": { - "$ref": "#/definitions/Uint128" - }, - "yes": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - }, - "WasmMsg": { - "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", - "oneOf": [ - { - "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "execute" - ], - "properties": { - "execute": { - "type": "object", - "required": [ - "contract_addr", - "funds", - "msg" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "msg": { - "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "instantiate" - ], - "properties": { - "instantiate": { - "type": "object", - "required": [ - "code_id", - "funds", - "label", - "msg" - ], - "properties": { - "admin": { - "type": [ - "string", - "null" - ] - }, - "code_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "funds": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "label": { - "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", - "type": "string" - }, - "msg": { - "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", - "type": "object", - "required": [ - "migrate" - ], - "properties": { - "migrate": { - "type": "object", - "required": [ - "contract_addr", - "msg", - "new_code_id" - ], - "properties": { - "contract_addr": { - "type": "string" - }, - "msg": { - "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - }, - "new_code_id": { - "description": "the code_id of the new logic to place in the given contract", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "update_admin" - ], - "properties": { - "update_admin": { - "type": "object", - "required": [ - "admin", - "contract_addr" - ], - "properties": { - "admin": { - "type": "string" - }, - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", - "type": "object", - "required": [ - "clear_admin" - ], - "properties": { - "clear_admin": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ] - } - } - }, - "vote_hooks": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HooksResponse", - "type": "object", - "required": [ - "hooks" - ], - "properties": { - "hooks": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false } - } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "type": "string", + "enum": [] + }, + "migrate": null, + "sudo": null, + "responses": {} } diff --git a/contracts/external/dao-migrator/src/contract.rs b/contracts/external/dao-migrator/src/contract.rs index 3610ab9be..625d0276b 100644 --- a/contracts/external/dao-migrator/src/contract.rs +++ b/contracts/external/dao-migrator/src/contract.rs @@ -115,6 +115,7 @@ fn execute_migration_v1_v2( close_proposal_on_execution_failure: proposal_params .close_proposal_on_execution_failure, pre_propose_info: proposal_params.pre_propose_info, + veto: proposal_params.veto, }, ), ), diff --git a/contracts/external/dao-migrator/src/testing/setup.rs b/contracts/external/dao-migrator/src/testing/setup.rs index 60959d85d..becb49522 100644 --- a/contracts/external/dao-migrator/src/testing/setup.rs +++ b/contracts/external/dao-migrator/src/testing/setup.rs @@ -275,6 +275,7 @@ pub fn execute_migration( close_proposal_on_execution_failure: true, pre_propose_info: dao_voting::pre_propose::PreProposeInfo::AnyoneMayPropose {}, + veto: None, }, ) }) diff --git a/contracts/external/dao-migrator/src/testing/state_helpers.rs b/contracts/external/dao-migrator/src/testing/state_helpers.rs index 451a28821..13dbb82ef 100644 --- a/contracts/external/dao-migrator/src/testing/state_helpers.rs +++ b/contracts/external/dao-migrator/src/testing/state_helpers.rs @@ -54,6 +54,7 @@ pub fn query_proposal_v1( status: v1_status_to_v2(proposal.status), votes: v1_votes_to_v2(proposal.votes), allow_revoting: proposal.allow_revoting, + veto: None, }; (proposal_count, proposal) diff --git a/contracts/external/dao-migrator/src/testing/test_migration.rs b/contracts/external/dao-migrator/src/testing/test_migration.rs index cf936f7e4..691e00c9f 100644 --- a/contracts/external/dao-migrator/src/testing/test_migration.rs +++ b/contracts/external/dao-migrator/src/testing/test_migration.rs @@ -189,6 +189,7 @@ fn test_duplicate_proposal_params() { ProposalParams { close_proposal_on_execution_failure: true, pre_propose_info: dao_voting::pre_propose::PreProposeInfo::AnyoneMayPropose {}, + veto: None, }, ), ( @@ -196,6 +197,7 @@ fn test_duplicate_proposal_params() { ProposalParams { close_proposal_on_execution_failure: true, pre_propose_info: dao_voting::pre_propose::PreProposeInfo::AnyoneMayPropose {}, + veto: None, }, ), ]; diff --git a/contracts/external/dao-migrator/src/types.rs b/contracts/external/dao-migrator/src/types.rs index 837ca736e..4663bdee2 100644 --- a/contracts/external/dao-migrator/src/types.rs +++ b/contracts/external/dao-migrator/src/types.rs @@ -1,5 +1,6 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Uint128}; +use dao_voting::veto::VetoConfig; use crate::ContractError; @@ -46,6 +47,7 @@ impl V2CodeIds { pub struct ProposalParams { pub close_proposal_on_execution_failure: bool, pub pre_propose_info: dao_voting::pre_propose::PreProposeInfo, + pub veto: Option, } #[cw_serde] diff --git a/contracts/external/dao-migrator/src/utils/state_queries.rs b/contracts/external/dao-migrator/src/utils/state_queries.rs index 52fcc8bfd..338281d93 100644 --- a/contracts/external/dao-migrator/src/utils/state_queries.rs +++ b/contracts/external/dao-migrator/src/utils/state_queries.rs @@ -83,6 +83,7 @@ pub fn query_proposal_v1( status: v1_status_to_v2(proposal.status), votes: v1_votes_to_v2(proposal.votes), allow_revoting: proposal.allow_revoting, + veto: None, }) }) .collect::, ContractError>>( diff --git a/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json b/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json index a4e449fb7..9a668eb2a 100644 --- a/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json +++ b/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json @@ -751,6 +751,53 @@ } ] }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, "GovMsg": { "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", "oneOf": [ @@ -1104,6 +1151,35 @@ "enum": [ "execution_failed" ] + }, + { + "description": "The proposal is timelocked. Only the configured vetoer can execute or veto until the timelock expires.", + "type": "object", + "required": [ + "veto_timelock" + ], + "properties": { + "veto_timelock": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "The proposal has been vetoed.", + "type": "string", + "enum": [ + "vetoed" + ] } ] }, diff --git a/contracts/pre-propose/dao-pre-propose-approval-single/src/tests.rs b/contracts/pre-propose/dao-pre-propose-approval-single/src/tests.rs index 2dbbce897..07afdf1da 100644 --- a/contracts/pre-propose/dao-pre-propose-approval-single/src/tests.rs +++ b/contracts/pre-propose/dao-pre-propose-approval-single/src/tests.rs @@ -77,6 +77,7 @@ fn get_default_proposal_module_instantiate( }, }, close_proposal_on_execution_failure: false, + veto: None, } } @@ -1362,6 +1363,7 @@ fn test_instantiate_with_zero_native_deposit() { }, }, close_proposal_on_execution_failure: false, + veto: None, } }; @@ -1426,6 +1428,7 @@ fn test_instantiate_with_zero_cw20_deposit() { }, }, close_proposal_on_execution_failure: false, + veto: None, } }; diff --git a/contracts/pre-propose/dao-pre-propose-approver/schema/dao-pre-propose-approver.json b/contracts/pre-propose/dao-pre-propose-approver/schema/dao-pre-propose-approver.json index 975cc824a..860039bcf 100644 --- a/contracts/pre-propose/dao-pre-propose-approver/schema/dao-pre-propose-approver.json +++ b/contracts/pre-propose/dao-pre-propose-approver/schema/dao-pre-propose-approver.json @@ -302,6 +302,53 @@ "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", "type": "object" }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, "Status": { "oneOf": [ { @@ -345,6 +392,43 @@ "enum": [ "execution_failed" ] + }, + { + "description": "The proposal is timelocked. Only the configured vetoer can execute or veto until the timelock expires.", + "type": "object", + "required": [ + "veto_timelock" + ], + "properties": { + "veto_timelock": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "The proposal has been vetoed.", + "type": "string", + "enum": [ + "vetoed" + ] + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" } ] }, @@ -352,6 +436,10 @@ "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, "UncheckedDenom": { "description": "A denom that has not been checked to confirm it points to a valid asset.", "oneOf": [ diff --git a/contracts/pre-propose/dao-pre-propose-approver/src/contract.rs b/contracts/pre-propose/dao-pre-propose-approver/src/contract.rs index 4dddbb545..981cd30e6 100644 --- a/contracts/pre-propose/dao-pre-propose-approver/src/contract.rs +++ b/contracts/pre-propose/dao-pre-propose-approver/src/contract.rs @@ -180,7 +180,7 @@ pub fn execute_proposal_completed( .add_message(msg) .add_attribute("method", "execute_proposal_completed_hook") .add_attribute("proposal", proposal_id.to_string())), - None => Err(PreProposeError::NotClosedOrExecuted { status: new_status }), + None => Err(PreProposeError::NotCompleted { status: new_status }), } } diff --git a/contracts/pre-propose/dao-pre-propose-approver/src/tests.rs b/contracts/pre-propose/dao-pre-propose-approver/src/tests.rs index 709f3fd28..630fcff33 100644 --- a/contracts/pre-propose/dao-pre-propose-approver/src/tests.rs +++ b/contracts/pre-propose/dao-pre-propose-approver/src/tests.rs @@ -101,6 +101,7 @@ fn get_proposal_module_approval_single_instantiate( }, }, close_proposal_on_execution_failure: false, + veto: None, } } @@ -133,6 +134,7 @@ fn get_proposal_module_approver_instantiate( }, }, close_proposal_on_execution_failure: false, + veto: None, } } diff --git a/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json b/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json index 0ff3a3b54..2cd69baa6 100644 --- a/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json +++ b/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json @@ -669,6 +669,53 @@ "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", "type": "object" }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, "GovMsg": { "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", "oneOf": [ @@ -1059,6 +1106,35 @@ "enum": [ "execution_failed" ] + }, + { + "description": "The proposal is timelocked. Only the configured vetoer can execute or veto until the timelock expires.", + "type": "object", + "required": [ + "veto_timelock" + ], + "properties": { + "veto_timelock": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "The proposal has been vetoed.", + "type": "string", + "enum": [ + "vetoed" + ] } ] }, diff --git a/contracts/pre-propose/dao-pre-propose-multiple/src/tests.rs b/contracts/pre-propose/dao-pre-propose-multiple/src/tests.rs index 241bb039f..4a8825a5c 100644 --- a/contracts/pre-propose/dao-pre-propose-multiple/src/tests.rs +++ b/contracts/pre-propose/dao-pre-propose-multiple/src/tests.rs @@ -77,6 +77,7 @@ fn get_default_proposal_module_instantiate( }, }, close_proposal_on_execution_failure: false, + veto: None, } } @@ -1065,6 +1066,7 @@ fn test_instantiate_with_zero_native_deposit() { }, }, close_proposal_on_execution_failure: false, + veto: None, } }; @@ -1127,6 +1129,7 @@ fn test_instantiate_with_zero_cw20_deposit() { }, }, close_proposal_on_execution_failure: false, + veto: None, } }; diff --git a/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json b/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json index 976940407..8a9eff35a 100644 --- a/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json +++ b/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json @@ -669,6 +669,53 @@ "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", "type": "object" }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, "GovMsg": { "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", "oneOf": [ @@ -1023,6 +1070,35 @@ "enum": [ "execution_failed" ] + }, + { + "description": "The proposal is timelocked. Only the configured vetoer can execute or veto until the timelock expires.", + "type": "object", + "required": [ + "veto_timelock" + ], + "properties": { + "veto_timelock": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "The proposal has been vetoed.", + "type": "string", + "enum": [ + "vetoed" + ] } ] }, diff --git a/contracts/pre-propose/dao-pre-propose-single/src/tests.rs b/contracts/pre-propose/dao-pre-propose-single/src/tests.rs index 0cd459811..d766ce5cc 100644 --- a/contracts/pre-propose/dao-pre-propose-single/src/tests.rs +++ b/contracts/pre-propose/dao-pre-propose-single/src/tests.rs @@ -75,6 +75,7 @@ fn get_default_proposal_module_instantiate( }, }, close_proposal_on_execution_failure: false, + veto: None, } } @@ -1001,6 +1002,7 @@ fn test_instantiate_with_zero_native_deposit() { }, }, close_proposal_on_execution_failure: false, + veto: None, } }; @@ -1063,6 +1065,7 @@ fn test_instantiate_with_zero_cw20_deposit() { }, }, close_proposal_on_execution_failure: false, + veto: None, } }; diff --git a/contracts/proposal/dao-proposal-multiple/Cargo.toml b/contracts/proposal/dao-proposal-multiple/Cargo.toml index a7016b1ee..92cc9004a 100644 --- a/contracts/proposal/dao-proposal-multiple/Cargo.toml +++ b/contracts/proposal/dao-proposal-multiple/Cargo.toml @@ -43,6 +43,7 @@ dao-pre-propose-multiple = { workspace = true } voting-v1 = { workspace = true } [dev-dependencies] +anyhow = { workspace = true } cw-multi-test = { workspace = true } dao-voting-cw4 = { workspace = true } dao-voting-cw20-balance = { workspace = true } diff --git a/contracts/proposal/dao-proposal-multiple/README.md b/contracts/proposal/dao-proposal-multiple/README.md index c634a1d16..0d6f2c122 100644 --- a/contracts/proposal/dao-proposal-multiple/README.md +++ b/contracts/proposal/dao-proposal-multiple/README.md @@ -52,3 +52,55 @@ handling a hook. The proposals may be configured to allow revoting. In such cases, users are able to change their vote as long as the proposal is still open. Revoting for the currently cast option will return an error. + +## Veto + +Proposals may be configured with an optional `VetoConfig` - a configuration describing +the veto flow. + +VetoConfig timelock period enables a party (such as an oversight committee DAO) +to hold the main DAO accountable by vetoing proposals once (and potentially +before) they are passed for a given timelock period. + +No actions from DAO members are allowed during the timelock period. + +After the timelock expires, the proposal can be executed normally. + +`VetoConfig` contains the following fields: + +### `timelock_duration` + +Timelock duration (`cw_utils::Duration`) describes the duration of timelock +in blocks or seconds. + +The delay duration is added to the proposal's expiration to get the timelock +expiration (`Expiration`) used for the new proposal state of `VetoTimelock { +expiration: Expiration }`. + +If the vetoer address is another DAO, this duration should be carefully +considered based on of the vetoer DAO's voting period. + +### `vetoer` + +Vetoer (`String`) is the address of the account allowed to veto the proposals +that are in `VetoTimelock` state. + +Vetoer address can be updated via a regular proposal config update. + +If you want the `vetoer` role to be shared between multiple organizations or +individuals, a +[cw1-whitelist](https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw1-whitelist) +contract address can be used to allow multiple accounts to veto the prop. + +### `early_execute` + +Early execute (`bool`) is a flag used to indicate whether the vetoer can execute +the proposals before the timelock period is expired. The proposals still need to +be passed and in the `VetoTimelock` state in order for this to be possible. This +may prevent the veto flow from consistently lengthening the governance process. + +### `veto_before_passed` + +Veto before passed (`bool`) is a flag used to indicate whether the vetoer +can veto a proposal before it passes. Votes may still be cast until the +specified proposal expiration, even once vetoed. diff --git a/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json b/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json index 0ecfcf955..59bdd0924 100644 --- a/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json +++ b/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json @@ -54,6 +54,17 @@ } ] }, + "veto": { + "description": "Optional veto configuration for proposal execution. If set, proposals can only be executed after the timelock delay expiration. During this period an oversight account (`veto.vetoer`) can veto the proposal.", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] + }, "voting_strategy": { "description": "Voting params configuration", "allOf": [ @@ -288,6 +299,38 @@ "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to lock a proposal for after its expiration to allow the vetoer to veto.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false + }, "VotingStrategy": { "description": "Determines how many choices may be selected.", "oneOf": [ @@ -430,6 +473,31 @@ }, "additionalProperties": false }, + { + "description": "Callable only if veto is configured", + "type": "object", + "required": [ + "veto" + ], + "properties": { + "veto": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "description": "The ID of the proposal to veto.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Closes a proposal that has failed (either not passed or timed out). If applicable this will cause the proposal deposit associated wth said proposal to be returned.", "type": "object", @@ -508,6 +576,17 @@ "description": "If set to true only members may execute passed proposals. Otherwise, any address may execute a passed proposal. Applies to all outstanding and future proposals.", "type": "boolean" }, + "veto": { + "description": "Optional time delay on proposal execution, during which the proposal may be vetoed.", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] + }, "voting_strategy": { "description": "The new proposal voting strategy. This will only apply to proposals created after the config update.", "allOf": [ @@ -1447,6 +1526,38 @@ "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", "type": "string" }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to lock a proposal for after its expiration to allow the vetoer to veto.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false + }, "VoteOption": { "type": "string", "enum": [ @@ -1959,6 +2070,17 @@ "$ref": "#/definitions/PreProposeInfo" } ] + }, + "veto": { + "description": "This field was not present in DAO DAO v1. To migrate, a value must be specified.\n\noptional configuration for veto feature", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false @@ -2041,6 +2163,40 @@ } } }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, "ModuleInstantiateInfo": { "description": "Information needed to instantiate a module.", "type": "object", @@ -2133,6 +2289,38 @@ "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" + }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to lock a proposal for after its expiration to allow the vetoer to veto.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false } } }, @@ -2191,6 +2379,17 @@ "description": "If set to true only members may execute passed proposals. Otherwise, any address may execute a passed proposal.", "type": "boolean" }, + "veto": { + "description": "Optional veto configuration. If set to `None`, veto option is disabled. Otherwise contains the configuration for veto flow.", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] + }, "voting_strategy": { "description": "The threshold a proposal must reach to complete.", "allOf": [ @@ -2276,6 +2475,38 @@ } ] }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to lock a proposal for after its expiration to allow the vetoer to veto.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false + }, "VotingStrategy": { "description": "Determines how many choices may be selected.", "oneOf": [ @@ -2731,6 +2962,40 @@ } ] }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, "Empty": { "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", "type": "object" @@ -3018,6 +3283,7 @@ } }, "description": { + "description": "The main body of the proposal text", "type": "string" }, "expiration": { @@ -3054,7 +3320,7 @@ "minimum": 0.0 }, "status": { - "description": "Prosal status (Open, rejected, executed, execution failed, closed, passed)", + "description": "The proposal status", "allOf": [ { "$ref": "#/definitions/Status" @@ -3062,6 +3328,7 @@ ] }, "title": { + "description": "The title of the proposal", "type": "string" }, "total_power": { @@ -3072,6 +3339,17 @@ } ] }, + "veto": { + "description": "Optional veto configuration. If set to `None`, veto option is disabled. Otherwise contains the configuration for veto flow.", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] + }, "votes": { "description": "The vote tally.", "allOf": [ @@ -3284,6 +3562,35 @@ "enum": [ "execution_failed" ] + }, + { + "description": "The proposal is timelocked. Only the configured vetoer can execute or veto until the timelock expires.", + "type": "object", + "required": [ + "veto_timelock" + ], + "properties": { + "veto_timelock": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "The proposal has been vetoed.", + "type": "string", + "enum": [ + "vetoed" + ] } ] }, @@ -3303,6 +3610,38 @@ "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", "type": "string" }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to lock a proposal for after its expiration to allow the vetoer to veto.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false + }, "VoteOption": { "type": "string", "enum": [ @@ -3912,6 +4251,40 @@ } ] }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, "Empty": { "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", "type": "object" @@ -4199,6 +4572,7 @@ } }, "description": { + "description": "The main body of the proposal text", "type": "string" }, "expiration": { @@ -4235,7 +4609,7 @@ "minimum": 0.0 }, "status": { - "description": "Prosal status (Open, rejected, executed, execution failed, closed, passed)", + "description": "The proposal status", "allOf": [ { "$ref": "#/definitions/Status" @@ -4243,6 +4617,7 @@ ] }, "title": { + "description": "The title of the proposal", "type": "string" }, "total_power": { @@ -4253,6 +4628,17 @@ } ] }, + "veto": { + "description": "Optional veto configuration. If set to `None`, veto option is disabled. Otherwise contains the configuration for veto flow.", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] + }, "votes": { "description": "The vote tally.", "allOf": [ @@ -4446,6 +4832,35 @@ "enum": [ "execution_failed" ] + }, + { + "description": "The proposal is timelocked. Only the configured vetoer can execute or veto until the timelock expires.", + "type": "object", + "required": [ + "veto_timelock" + ], + "properties": { + "veto_timelock": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "The proposal has been vetoed.", + "type": "string", + "enum": [ + "vetoed" + ] } ] }, @@ -4465,6 +4880,38 @@ "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", "type": "string" }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to lock a proposal for after its expiration to allow the vetoer to veto.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false + }, "VoteOption": { "type": "string", "enum": [ @@ -5050,6 +5497,40 @@ } ] }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, "Empty": { "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", "type": "object" @@ -5337,6 +5818,7 @@ } }, "description": { + "description": "The main body of the proposal text", "type": "string" }, "expiration": { @@ -5373,7 +5855,7 @@ "minimum": 0.0 }, "status": { - "description": "Prosal status (Open, rejected, executed, execution failed, closed, passed)", + "description": "The proposal status", "allOf": [ { "$ref": "#/definitions/Status" @@ -5381,6 +5863,7 @@ ] }, "title": { + "description": "The title of the proposal", "type": "string" }, "total_power": { @@ -5391,6 +5874,17 @@ } ] }, + "veto": { + "description": "Optional veto configuration. If set to `None`, veto option is disabled. Otherwise contains the configuration for veto flow.", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] + }, "votes": { "description": "The vote tally.", "allOf": [ @@ -5603,6 +6097,35 @@ "enum": [ "execution_failed" ] + }, + { + "description": "The proposal is timelocked. Only the configured vetoer can execute or veto until the timelock expires.", + "type": "object", + "required": [ + "veto_timelock" + ], + "properties": { + "veto_timelock": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "The proposal has been vetoed.", + "type": "string", + "enum": [ + "vetoed" + ] } ] }, @@ -5622,6 +6145,38 @@ "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", "type": "string" }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to lock a proposal for after its expiration to allow the vetoer to veto.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false + }, "VoteOption": { "type": "string", "enum": [ diff --git a/contracts/proposal/dao-proposal-multiple/src/contract.rs b/contracts/proposal/dao-proposal-multiple/src/contract.rs index c1af47ced..632060f53 100644 --- a/contracts/proposal/dao-proposal-multiple/src/contract.rs +++ b/contracts/proposal/dao-proposal-multiple/src/contract.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Reply, Response, + to_json_binary, Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Order, Reply, Response, StdResult, Storage, SubMsg, WasmMsg, }; @@ -9,10 +9,12 @@ use cw2::set_contract_version; use cw_hooks::Hooks; use cw_storage_plus::Bound; use cw_utils::{parse_reply_instantiate_data, Duration}; -use dao_hooks::proposal::{new_proposal_hooks, proposal_status_changed_hooks}; +use dao_hooks::proposal::{ + new_proposal_hooks, proposal_completed_hooks, proposal_status_changed_hooks, +}; use dao_hooks::vote::new_vote_hooks; use dao_interface::voting::IsActiveResponse; -use dao_pre_propose_multiple::contract::ExecuteMsg as PreProposeMsg; +use dao_voting::veto::{VetoConfig, VetoError}; use dao_voting::{ multiple_choice::{ MultipleChoiceOptions, MultipleChoiceVote, MultipleChoiceVotes, VotingStrategy, @@ -60,6 +62,11 @@ pub fn instantiate( .pre_propose_info .into_initial_policy_and_messages(dao.clone())?; + // if veto is configured, validate its fields + if let Some(veto_config) = &msg.veto { + veto_config.validate(&deps.as_ref(), &max_voting_period)?; + }; + let config = Config { voting_strategy: msg.voting_strategy, min_voting_period, @@ -68,6 +75,7 @@ pub fn instantiate( allow_revoting: msg.allow_revoting, dao, close_proposal_on_execution_failure: msg.close_proposal_on_execution_failure, + veto: msg.veto, }; // Initialize proposal count to zero so that queries return zero @@ -110,6 +118,7 @@ pub fn execute( rationale, } => execute_vote(deps, env, info, proposal_id, vote, rationale), ExecuteMsg::Execute { proposal_id } => execute_execute(deps, env, info, proposal_id), + ExecuteMsg::Veto { proposal_id } => execute_veto(deps, env, info, proposal_id), ExecuteMsg::Close { proposal_id } => execute_close(deps, env, info, proposal_id), ExecuteMsg::UpdateConfig { voting_strategy, @@ -119,6 +128,7 @@ pub fn execute( allow_revoting, dao, close_proposal_on_execution_failure, + veto, } => execute_update_config( deps, info, @@ -129,6 +139,7 @@ pub fn execute( allow_revoting, dao, close_proposal_on_execution_failure, + veto, ), ExecuteMsg::UpdatePreProposeInfo { info: new_info } => { execute_update_proposal_creation_policy(deps, info, new_info) @@ -217,6 +228,7 @@ pub fn execute_propose( votes: MultipleChoiceVotes::zero(checked_multiple_choice_options.len()), allow_revoting: config.allow_revoting, choices: checked_multiple_choice_options, + veto: config.veto, }; // Update the proposal's status. Addresses case where proposal // expires on the same block as it is created. @@ -259,6 +271,79 @@ pub fn execute_propose( .add_attribute("status", proposal.status.to_string())) } +pub fn execute_veto( + deps: DepsMut, + env: Env, + info: MessageInfo, + proposal_id: u64, +) -> Result { + let mut prop = PROPOSALS + .may_load(deps.storage, proposal_id)? + .ok_or(ContractError::NoSuchProposal { id: proposal_id })?; + + // ensure status is up to date + prop.update_status(&env.block)?; + let old_status = prop.status; + + let veto_config = prop + .veto + .as_ref() + .ok_or(VetoError::NoVetoConfiguration {})?; + + // Check sender is vetoer + veto_config.check_is_vetoer(&info)?; + + match prop.status { + Status::Open => { + // can only veto an open proposal if veto_before_passed is enabled. + veto_config.check_veto_before_passed_enabled()?; + } + Status::Passed => { + // if this proposal has veto configured but is in the passed state, + // the timelock already expired, so provide a more specific error. + return Err(ContractError::VetoError(VetoError::TimelockExpired {})); + } + Status::VetoTimelock { expiration } => { + // vetoer can veto the proposal iff the timelock is active/not + // expired. this should never happen since the status updates to + // passed after the timelock expires, but let's check anyway. + if expiration.is_expired(&env.block) { + return Err(ContractError::VetoError(VetoError::TimelockExpired {})); + } + } + // generic status error if the proposal has any other status. + _ => { + return Err(ContractError::VetoError(VetoError::InvalidProposalStatus { + status: prop.status.to_string(), + })); + } + } + + // Update proposal status to vetoed + prop.status = Status::Vetoed; + PROPOSALS.save(deps.storage, proposal_id, &prop)?; + + // Add proposal status change hooks + let proposal_status_changed_hooks = proposal_status_changed_hooks( + PROPOSAL_HOOKS, + deps.storage, + proposal_id, + old_status.to_string(), + prop.status.to_string(), + )?; + + // Add prepropose / deposit module hook which will handle deposit refunds. + let proposal_creation_policy = CREATION_POLICY.load(deps.storage)?; + let proposal_completed_hooks = + proposal_completed_hooks(proposal_creation_policy, proposal_id, prop.status)?; + + Ok(Response::new() + .add_attribute("action", "veto") + .add_attribute("proposal_id", proposal_id.to_string()) + .add_submessages(proposal_status_changed_hooks) + .add_submessages(proposal_completed_hooks)) +} + pub fn execute_vote( deps: DepsMut, env: Env, @@ -375,18 +460,52 @@ pub fn execute_execute( &config.dao, Some(prop.start_height), )?; - if power.is_zero() { + + // if there is no veto config, then caller is not the vetoer + // if there is, we validate the caller addr + let vetoer_call = prop + .veto + .as_ref() + .map_or(false, |veto_config| veto_config.vetoer == info.sender); + + if power.is_zero() && !vetoer_call { return Err(ContractError::Unauthorized {}); } } - // Check here that the proposal is passed. Allow it to be - // executed even if it is expired so long as it passed during its - // voting period. + // Check here that the proposal is passed or timelocked. + // Allow it to be executed even if it is expired so long + // as it passed during its voting period. Allow it to be + // executed in timelock state if early_execute is enabled + // and the sender is the vetoer. prop.update_status(&env.block)?; let old_status = prop.status; - if prop.status != Status::Passed { - return Err(ContractError::NotPassed {}); + match &prop.status { + Status::Passed => (), + Status::VetoTimelock { expiration } => { + let veto_config = prop + .veto + .as_ref() + .ok_or(VetoError::NoVetoConfiguration {})?; + + // Check if the sender is the vetoer + match veto_config.vetoer == info.sender { + // if sender is the vetoer we validate the early exec flag + true => veto_config.check_early_execute_enabled()?, + // otherwise timelock must be expired in order to execute + false => { + // it should never be expired here since the status updates + // to passed after the timelock expires, but let's check + // anyway. i.e. this error should always be returned. + if !expiration.is_expired(&env.block) { + return Err(ContractError::VetoError(VetoError::Timelocked {})); + } + } + } + } + _ => { + return Err(ContractError::NotPassed {}); + } } prop.status = Status::Executed; @@ -419,7 +538,7 @@ pub fn execute_execute( Response::default() }; - let hooks = proposal_status_changed_hooks( + let proposal_status_changed_hooks = proposal_status_changed_hooks( PROPOSAL_HOOKS, deps.storage, proposal_id, @@ -429,28 +548,12 @@ pub fn execute_execute( // Add prepropose / deposit module hook which will handle deposit refunds. let proposal_creation_policy = CREATION_POLICY.load(deps.storage)?; - let hooks = match proposal_creation_policy { - ProposalCreationPolicy::Anyone {} => hooks, - ProposalCreationPolicy::Module { addr } => { - let msg = to_json_binary(&PreProposeMsg::ProposalCompletedHook { - proposal_id, - new_status: prop.status, - })?; - let mut hooks = hooks; - hooks.push(SubMsg::reply_on_error( - WasmMsg::Execute { - contract_addr: addr.into_string(), - msg, - funds: vec![], - }, - failed_pre_propose_module_hook_id(), - )); - hooks - } - }; + let proposal_completed_hooks = + proposal_completed_hooks(proposal_creation_policy, proposal_id, prop.status)?; Ok(response - .add_submessages(hooks) + .add_submessages(proposal_status_changed_hooks) + .add_submessages(proposal_completed_hooks) .add_attribute("action", "execute") .add_attribute("sender", info.sender) .add_attribute("proposal_id", proposal_id.to_string()) @@ -478,7 +581,7 @@ pub fn execute_close( PROPOSALS.save(deps.storage, proposal_id, &prop)?; - let hooks = proposal_status_changed_hooks( + let proposal_status_changed_hooks = proposal_status_changed_hooks( PROPOSAL_HOOKS, deps.storage, proposal_id, @@ -488,27 +591,12 @@ pub fn execute_close( // Add prepropose / deposit module hook which will handle deposit refunds. let proposal_creation_policy = CREATION_POLICY.load(deps.storage)?; - let hooks = match proposal_creation_policy { - ProposalCreationPolicy::Anyone {} => hooks, - ProposalCreationPolicy::Module { addr } => { - let msg = to_json_binary(&PreProposeMsg::ProposalCompletedHook { - proposal_id, - new_status: prop.status, - })?; - let mut hooks = hooks; - hooks.push(SubMsg::reply_on_error( - WasmMsg::Execute { - contract_addr: addr.into_string(), - msg, - funds: vec![], - }, - failed_pre_propose_module_hook_id(), - )); - hooks - } - }; + let proposal_completed_hooks = + proposal_completed_hooks(proposal_creation_policy, proposal_id, prop.status)?; + Ok(Response::default() - .add_submessages(hooks) + .add_submessages(proposal_status_changed_hooks) + .add_submessages(proposal_completed_hooks) .add_attribute("action", "close") .add_attribute("sender", info.sender) .add_attribute("proposal_id", proposal_id.to_string())) @@ -525,6 +613,7 @@ pub fn execute_update_config( allow_revoting: bool, dao: String, close_proposal_on_execution_failure: bool, + veto: Option, ) -> Result { let config = CONFIG.load(deps.storage)?; @@ -540,6 +629,11 @@ pub fn execute_update_config( let (min_voting_period, max_voting_period) = validate_voting_period(min_voting_period, max_voting_period)?; + // if veto is configured, validate its fields + if let Some(veto_config) = &veto { + veto_config.validate(&deps.as_ref(), &max_voting_period)?; + }; + CONFIG.save( deps.storage, &Config { @@ -550,6 +644,7 @@ pub fn execute_update_config( allow_revoting, dao, close_proposal_on_execution_failure, + veto, }, )?; @@ -845,7 +940,7 @@ pub fn query_list_votes( let votes = BALLOTS .prefix(proposal_id) - .range(deps.storage, min, None, cosmwasm_std::Order::Ascending) + .range(deps.storage, min, None, Order::Ascending) .take(limit as usize) .map(|item| { let (voter, ballot) = item?; diff --git a/contracts/proposal/dao-proposal-multiple/src/error.rs b/contracts/proposal/dao-proposal-multiple/src/error.rs index a1d1df105..76fe05724 100644 --- a/contracts/proposal/dao-proposal-multiple/src/error.rs +++ b/contracts/proposal/dao-proposal-multiple/src/error.rs @@ -3,10 +3,10 @@ use std::u64; use cosmwasm_std::StdError; use cw_hooks::HookError; use cw_utils::ParseReplyError; -use dao_voting::{reply::error::TagError, threshold::ThresholdError}; +use dao_voting::{reply::error::TagError, threshold::ThresholdError, veto::VetoError}; use thiserror::Error; -#[derive(Error, Debug)] +#[derive(Error, Debug, PartialEq)] pub enum ContractError { #[error("{0}")] Std(#[from] StdError), @@ -17,6 +17,9 @@ pub enum ContractError { #[error("{0}")] HookError(#[from] HookError), + #[error(transparent)] + VetoError(#[from] VetoError), + #[error("Unauthorized")] Unauthorized {}, diff --git a/contracts/proposal/dao-proposal-multiple/src/msg.rs b/contracts/proposal/dao-proposal-multiple/src/msg.rs index a79fca805..482ffadd7 100644 --- a/contracts/proposal/dao-proposal-multiple/src/msg.rs +++ b/contracts/proposal/dao-proposal-multiple/src/msg.rs @@ -4,6 +4,7 @@ use dao_dao_macros::proposal_module_query; use dao_voting::{ multiple_choice::{MultipleChoiceOptions, MultipleChoiceVote, VotingStrategy}, pre_propose::PreProposeInfo, + veto::VetoConfig, }; #[cw_serde] @@ -37,6 +38,12 @@ pub struct InstantiateMsg { /// remain open until the DAO's treasury was large enough for it to be /// executed. pub close_proposal_on_execution_failure: bool, + /// Optional veto configuration for proposal execution. + /// If set, proposals can only be executed after the timelock + /// delay expiration. + /// During this period an oversight account (`veto.vetoer`) can + /// veto the proposal. + pub veto: Option, } #[cw_serde] @@ -74,6 +81,11 @@ pub enum ExecuteMsg { /// The ID of the proposal to execute. proposal_id: u64, }, + /// Callable only if veto is configured + Veto { + /// The ID of the proposal to veto. + proposal_id: u64, + }, /// Closes a proposal that has failed (either not passed or timed /// out). If applicable this will cause the proposal deposit /// associated wth said proposal to be returned. @@ -116,6 +128,9 @@ pub enum ExecuteMsg { /// remain open until the DAO's treasury was large enough for it to be /// executed. close_proposal_on_execution_failure: bool, + /// Optional time delay on proposal execution, during which the + /// proposal may be vetoed. + veto: Option, }, /// Updates the sender's rationale for their vote on the specified /// proposal. Errors if no vote vote has been cast. @@ -217,6 +232,11 @@ pub enum MigrateMsg { /// no deposit or membership checks when submitting a proposal. The "ModuleMayPropose" /// option allows for instantiating a prepropose module which will handle deposit verification and return logic. pre_propose_info: PreProposeInfo, + /// This field was not present in DAO DAO v1. To migrate, a + /// value must be specified. + /// + /// optional configuration for veto feature + veto: Option, }, FromCompatible {}, } diff --git a/contracts/proposal/dao-proposal-multiple/src/proposal.rs b/contracts/proposal/dao-proposal-multiple/src/proposal.rs index 4852993db..454a60762 100644 --- a/contracts/proposal/dao-proposal-multiple/src/proposal.rs +++ b/contracts/proposal/dao-proposal-multiple/src/proposal.rs @@ -1,3 +1,5 @@ +use std::ops::Add; + use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, BlockInfo, StdError, StdResult, Uint128}; use cw_utils::Expiration; @@ -6,6 +8,7 @@ use dao_voting::{ CheckedMultipleChoiceOption, MultipleChoiceOptionType, MultipleChoiceVotes, VotingStrategy, }, status::Status, + veto::VetoConfig, voting::does_vote_count_pass, }; @@ -13,7 +16,9 @@ use crate::query::ProposalResponse; #[cw_serde] pub struct MultipleChoiceProposal { + /// The title of the proposal pub title: String, + /// The main body of the proposal text pub description: String, /// The address that created this proposal. pub proposer: Addr, @@ -30,7 +35,7 @@ pub struct MultipleChoiceProposal { pub expiration: Expiration, /// The options to be chosen from in the vote. pub choices: Vec, - /// Prosal status (Open, rejected, executed, execution failed, closed, passed) + /// The proposal status pub status: Status, /// Voting settings (threshold, quorum, etc.) pub voting_strategy: VotingStrategy, @@ -43,6 +48,9 @@ pub struct MultipleChoiceProposal { /// When enabled, proposals can only be executed after the voting /// perid has ended and the proposal passed. pub allow_revoting: bool, + /// Optional veto configuration. If set to `None`, veto option + /// is disabled. Otherwise contains the configuration for veto flow. + pub veto: Option, } pub enum VoteResult { @@ -65,14 +73,35 @@ impl MultipleChoiceProposal { /// Gets the current status of the proposal. pub fn current_status(&self, block: &BlockInfo) -> StdResult { - if self.status == Status::Open && self.is_passed(block)? { - Ok(Status::Passed) - } else if self.status == Status::Open - && (self.expiration.is_expired(block) || self.is_rejected(block)?) - { - Ok(Status::Rejected) - } else { - Ok(self.status) + match self.status { + Status::Open if self.is_passed(block)? => match &self.veto { + // if prop is passed and veto is configured, calculate timelock + // expiration. if it's expired, this proposal has passed. + // otherwise, set status to `VetoTimelock`. + Some(veto_config) => { + let expiration = self.expiration.add(veto_config.timelock_duration)?; + + if expiration.is_expired(block) { + Ok(Status::Passed) + } else { + Ok(Status::VetoTimelock { expiration }) + } + } + // Otherwise the proposal is simply passed + None => Ok(Status::Passed), + }, + Status::Open if self.expiration.is_expired(block) || self.is_rejected(block)? => { + Ok(Status::Rejected) + } + Status::VetoTimelock { expiration } => { + // if prop timelock expired, proposal is now passed. + if expiration.is_expired(block) { + Ok(Status::Passed) + } else { + Ok(self.status) + } + } + _ => Ok(self.status), } } @@ -306,6 +335,7 @@ mod tests { votes, allow_revoting, min_voting_period: None, + veto: None, } } diff --git a/contracts/proposal/dao-proposal-multiple/src/state.rs b/contracts/proposal/dao-proposal-multiple/src/state.rs index f9c6baa59..2261f6d03 100644 --- a/contracts/proposal/dao-proposal-multiple/src/state.rs +++ b/contracts/proposal/dao-proposal-multiple/src/state.rs @@ -7,6 +7,7 @@ use cw_utils::Duration; use dao_voting::{ multiple_choice::{MultipleChoiceVote, VotingStrategy}, pre_propose::ProposalCreationPolicy, + veto::VetoConfig, }; /// The proposal module's configuration. @@ -43,6 +44,9 @@ pub struct Config { /// remain open until the DAO's treasury was large enough for it to be /// executed. pub close_proposal_on_execution_failure: bool, + /// Optional veto configuration. If set to `None`, veto option + /// is disabled. Otherwise contains the configuration for veto flow. + pub veto: Option, } // Each ballot stores a chosen vote and corresponding voting power and rationale. diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/adversarial_tests.rs b/contracts/proposal/dao-proposal-multiple/src/testing/adversarial_tests.rs index 11f052cd6..3c27c98d1 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/adversarial_tests.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/adversarial_tests.rs @@ -280,6 +280,7 @@ pub fn test_allow_voting_after_proposal_execution_pre_expiration_cw20() { false, ), close_proposal_on_execution_failure: true, + veto: None, }; let core_addr = instantiate_with_multiple_staked_balances_governance( diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs b/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs index c1430c2f9..e8b5744bf 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs @@ -130,6 +130,7 @@ where voting_strategy, close_proposal_on_execution_failure: true, pre_propose_info, + veto: None, }; let governance_addr = setup_governance(&mut app, instantiate, Some(initial_balances)); diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs index 3a0dc89e2..bd954913b 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs @@ -65,6 +65,7 @@ pub fn _get_default_token_dao_proposal_module_instantiate(app: &mut App) -> Inst false, ), close_proposal_on_execution_failure: true, + veto: None, } } @@ -81,6 +82,7 @@ fn _get_default_non_token_dao_proposal_module_instantiate(app: &mut App) -> Inst allow_revoting: false, pre_propose_info: get_pre_propose_info(app, None, false), close_proposal_on_execution_failure: true, + veto: None, } } diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs b/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs index e1b8dddde..8064733d1 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs @@ -8,6 +8,7 @@ use cw_multi_test::{next_block, App, BankSudo, Contract, ContractWrapper, Execut use cw_utils::Duration; use dao_interface::state::ProposalModule; use dao_interface::state::{Admin, ModuleInstantiateInfo}; +use dao_voting::veto::{VetoConfig, VetoError}; use dao_voting::{ deposit::{CheckedDepositInfo, DepositRefundPolicy, DepositToken, UncheckedDepositInfo}, multiple_choice::{ @@ -19,6 +20,7 @@ use dao_voting::{ status::Status, threshold::{ActiveThreshold, PercentageThreshold, Threshold}, }; +use std::ops::Add; use std::panic; use crate::{ @@ -122,6 +124,7 @@ fn test_propose() { min_voting_period: None, close_proposal_on_execution_failure: true, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); @@ -137,6 +140,7 @@ fn test_propose() { voting_strategy: voting_strategy.clone(), min_voting_period: None, close_proposal_on_execution_failure: true, + veto: None, }; assert_eq!(config, expected); @@ -177,6 +181,7 @@ fn test_propose() { }, allow_revoting: false, min_voting_period: None, + veto: None, }; assert_eq!(created.proposal, expected); @@ -201,6 +206,7 @@ fn test_propose_wrong_num_choices() { allow_revoting: false, voting_strategy: voting_strategy.clone(), pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); @@ -216,6 +222,7 @@ fn test_propose_wrong_num_choices() { allow_revoting: false, dao: core_addr, voting_strategy, + veto: None, }; assert_eq!(config, expected); @@ -277,6 +284,7 @@ fn test_proposal_count_initialized_to_zero() { only_members_execute: true, allow_revoting: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance(&mut app, msg, None); @@ -311,6 +319,7 @@ fn test_no_early_pass_with_min_duration() { allow_revoting: false, close_proposal_on_execution_failure: true, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance( @@ -405,6 +414,7 @@ fn test_propose_with_messages() { only_members_execute: true, allow_revoting: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance( @@ -441,6 +451,7 @@ fn test_propose_with_messages() { only_members_execute: false, allow_revoting: false, dao: "dao".to_string(), + veto: None, }; let wasm_msg = WasmMsg::Execute { @@ -524,6 +535,7 @@ fn test_min_duration_units_missmatch() { allow_revoting: false, close_proposal_on_execution_failure: true, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; instantiate_with_staked_balances_governance( &mut app, @@ -556,6 +568,7 @@ fn test_min_duration_larger_than_proposal_duration() { allow_revoting: false, close_proposal_on_execution_failure: true, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; instantiate_with_staked_balances_governance( &mut app, @@ -587,6 +600,7 @@ fn test_min_duration_same_as_proposal_duration() { allow_revoting: false, close_proposal_on_execution_failure: true, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance( @@ -707,6 +721,7 @@ fn test_voting_module_token_proposal_deposit_instantiate() { }), false, ), + veto: None, }; let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); @@ -782,6 +797,7 @@ fn test_different_token_proposal_deposit() { }), false, ), + veto: None, }; instantiate_with_staked_balances_governance(&mut app, instantiate, None); @@ -843,6 +859,7 @@ fn test_bad_token_proposal_deposit() { }), false, ), + veto: None, }; instantiate_with_staked_balances_governance(&mut app, instantiate, None); @@ -873,6 +890,7 @@ fn test_take_proposal_deposit() { }), false, ), + veto: None, }; let core_addr = instantiate_with_cw20_balances_governance( @@ -978,6 +996,7 @@ fn test_native_proposal_deposit() { }), false, ), + veto: None, }; let core_addr = instantiate_with_staked_balances_governance( @@ -1403,6 +1422,7 @@ fn test_cant_propose_zero_power() { }), false, ), + veto: None, }; let core_addr = instantiate_with_cw20_balances_governance( @@ -1567,6 +1587,7 @@ fn test_cant_execute_not_member() { allow_revoting: false, voting_strategy, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance( @@ -1657,6 +1678,7 @@ fn test_cant_execute_not_member_when_proposal_created() { allow_revoting: false, voting_strategy, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance( @@ -1774,6 +1796,7 @@ fn test_open_proposal_submission() { allow_revoting: false, close_proposal_on_execution_failure: true, pre_propose_info: get_pre_propose_info(&mut app, None, true), + veto: None, }; let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); let govmod = query_multiple_proposal_module(&app, &core_addr); @@ -1842,6 +1865,7 @@ fn test_open_proposal_submission() { votes: MultipleChoiceVotes { vote_weights: vec![Uint128::zero(); 3], }, + veto: None, }; assert_eq!(created.proposal, expected); @@ -2067,6 +2091,7 @@ fn test_execute_expired_proposal() { allow_revoting: false, voting_strategy, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance( @@ -2229,6 +2254,7 @@ fn test_update_config() { only_members_execute: false, allow_revoting: false, dao: dao.to_string(), + veto: None, }, &[], ) @@ -2248,6 +2274,7 @@ fn test_update_config() { only_members_execute: false, allow_revoting: false, dao: Addr::unchecked(CREATOR_ADDR).to_string(), + veto: None, }, &[], ) @@ -2265,6 +2292,7 @@ fn test_update_config() { only_members_execute: false, allow_revoting: false, dao: Addr::unchecked(CREATOR_ADDR), + veto: None, }; assert_eq!(govmod_config, expected); @@ -2283,6 +2311,7 @@ fn test_update_config() { only_members_execute: false, allow_revoting: false, dao: Addr::unchecked(CREATOR_ADDR).to_string(), + veto: None, }, &[], ) @@ -2358,6 +2387,7 @@ fn test_query_list_proposals() { allow_revoting: false, voting_strategy: voting_strategy.clone(), pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let gov_addr = instantiate_with_staked_balances_governance( &mut app, @@ -2438,6 +2468,7 @@ fn test_query_list_proposals() { }, allow_revoting: false, min_voting_period: None, + veto: None, }, }; assert_eq!(proposals_forward.proposals[0], expected); @@ -2466,6 +2497,7 @@ fn test_query_list_proposals() { }, allow_revoting: false, min_voting_period: None, + veto: None, }, }; assert_eq!(proposals_forward.proposals[0], expected); @@ -2491,6 +2523,7 @@ fn test_hooks() { allow_revoting: false, voting_strategy, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); @@ -2617,6 +2650,7 @@ fn test_active_threshold_absolute() { allow_revoting: false, voting_strategy, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staking_active_threshold( @@ -2744,6 +2778,7 @@ fn test_active_threshold_percent() { allow_revoting: false, voting_strategy, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; // 20% needed to be active, 20% of 100000000 is 20000000 @@ -2872,6 +2907,7 @@ fn test_active_threshold_none() { allow_revoting: false, voting_strategy, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = @@ -2982,6 +3018,7 @@ fn test_revoting() { }, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }, Some(vec![ Cw20Coin { @@ -3114,6 +3151,7 @@ fn test_allow_revoting_config_changes() { }, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }, Some(vec![ Cw20Coin { @@ -3171,6 +3209,7 @@ fn test_allow_revoting_config_changes() { quorum: PercentageThreshold::Majority {}, }, close_proposal_on_execution_failure: false, + veto: None, }, &[], ) @@ -3265,6 +3304,7 @@ fn test_revoting_same_vote_twice() { }, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }, Some(vec![ Cw20Coin { @@ -3359,6 +3399,7 @@ fn test_invalid_revote_does_not_invalidate_initial_vote() { }, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }, Some(vec![ Cw20Coin { @@ -3550,6 +3591,7 @@ fn test_close_failed_proposal() { allow_revoting: false, close_proposal_on_execution_failure: true, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staking_active_threshold(&mut app, instantiate, None, None); @@ -3684,6 +3726,7 @@ fn test_close_failed_proposal() { allow_revoting: false, dao: original.dao.to_string(), close_proposal_on_execution_failure: false, + veto: None, }) .unwrap(), funds: vec![], @@ -3798,6 +3841,7 @@ fn test_no_double_refund_on_execute_fail_and_close() { }), false, ), + veto: None, }; let core_addr = instantiate_with_staking_active_threshold( @@ -3976,6 +4020,7 @@ pub fn test_not_allow_voting_on_expired_proposal() { min_voting_period: None, close_proposal_on_execution_failure: true, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance( &mut app, @@ -4067,6 +4112,7 @@ fn test_next_proposal_id() { }, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }, Some(vec![ Cw20Coin { @@ -4138,6 +4184,7 @@ fn test_vote_with_rationale() { }, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }, Some(vec![ Cw20Coin { @@ -4234,6 +4281,7 @@ fn test_revote_with_rationale() { }, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }, Some(vec![ Cw20Coin { @@ -4388,6 +4436,7 @@ fn test_update_rationale() { }, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }, Some(vec![ Cw20Coin { @@ -4501,3 +4550,934 @@ fn test_update_rationale() { Some("This may be a good idea, but I'm not sure. YOLO".to_string()) ); } + +#[test] +fn test_open_proposal_passes_with_zero_timelock_veto_duration() { + let mut app = App::default(); + let timelock_duration = 0; + let veto_config = VetoConfig { + timelock_duration: Duration::Height(timelock_duration), + vetoer: "vetoer".to_string(), + early_execute: false, + veto_before_passed: true, + }; + + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + InstantiateMsg { + min_voting_period: None, + max_voting_period: Duration::Height(6), + only_members_execute: false, + allow_revoting: false, + voting_strategy: VotingStrategy::SingleChoice { + quorum: PercentageThreshold::Majority {}, + }, + close_proposal_on_execution_failure: false, + pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: Some(veto_config), + }, + Some(vec![ + Cw20Coin { + address: "a-1".to_string(), + amount: Uint128::new(110_000_000), + }, + Cw20Coin { + address: "a-2".to_string(), + amount: Uint128::new(100_000_000), + }, + ]), + ); + let govmod = query_multiple_proposal_module(&app, &core_addr); + + let proposal_module = query_multiple_proposal_module(&app, &core_addr); + + let next_proposal_id: u64 = app + .wrap() + .query_wasm_smart(&proposal_module, &QueryMsg::NextProposalId {}) + .unwrap(); + assert_eq!(next_proposal_id, 1); + + let options = vec![ + MultipleChoiceOption { + description: "multiple choice option 1".to_string(), + msgs: vec![], + title: "title".to_string(), + }, + MultipleChoiceOption { + description: "multiple choice option 2".to_string(), + msgs: vec![], + title: "title".to_string(), + }, + ]; + let mc_options = MultipleChoiceOptions { options }; + + // Create a basic proposal with 2 options + app.execute_contract( + Addr::unchecked("a-1"), + proposal_module.clone(), + &ExecuteMsg::Propose { + title: "A simple text proposal".to_string(), + description: "A simple text proposal".to_string(), + choices: mc_options, + proposer: None, + }, + &[], + ) + .unwrap(); + + // zero duration timelock goes straight to passed status + app.execute_contract( + Addr::unchecked("a-1"), + proposal_module.clone(), + &ExecuteMsg::Vote { + proposal_id: 1, + vote: MultipleChoiceVote { option_id: 0 }, + rationale: None, + }, + &[], + ) + .unwrap(); + + // pass enough time to expire the proposal voting + app.update_block(|b| b.height += 7); + + let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); + + assert_eq!(proposal.proposal.status, Status::Passed {},); + + let err: ContractError = app + .execute_contract( + Addr::unchecked("vetoer"), + proposal_module.clone(), + &ExecuteMsg::Veto { proposal_id: 1 }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!(err, ContractError::VetoError(VetoError::TimelockExpired {})); +} + +#[test] +fn test_veto_non_existing_prop_id() { + let mut app = App::default(); + let timelock_duration = 0; + let veto_config = VetoConfig { + timelock_duration: Duration::Height(timelock_duration), + vetoer: "vetoer".to_string(), + early_execute: false, + veto_before_passed: true, + }; + + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + InstantiateMsg { + min_voting_period: None, + max_voting_period: Duration::Height(6), + only_members_execute: false, + allow_revoting: false, + voting_strategy: VotingStrategy::SingleChoice { + quorum: PercentageThreshold::Majority {}, + }, + close_proposal_on_execution_failure: false, + pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: Some(veto_config), + }, + Some(vec![ + Cw20Coin { + address: "a-1".to_string(), + amount: Uint128::new(110_000_000), + }, + Cw20Coin { + address: "a-2".to_string(), + amount: Uint128::new(100_000_000), + }, + ]), + ); + + let proposal_module = query_multiple_proposal_module(&app, &core_addr); + + // veto from non open/passed/veto state should return an error + let err: ContractError = app + .execute_contract( + Addr::unchecked("vetoer"), + proposal_module.clone(), + &ExecuteMsg::Veto { proposal_id: 69 }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!(err, ContractError::NoSuchProposal { id: 69 }); +} + +#[test] +fn test_veto_with_no_veto_configuration() { + let mut app = App::default(); + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + InstantiateMsg { + min_voting_period: None, + max_voting_period: Duration::Height(6), + only_members_execute: false, + allow_revoting: false, + voting_strategy: VotingStrategy::SingleChoice { + quorum: PercentageThreshold::Majority {}, + }, + close_proposal_on_execution_failure: false, + pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, + }, + Some(vec![ + Cw20Coin { + address: "a-1".to_string(), + amount: Uint128::new(110_000_000), + }, + Cw20Coin { + address: "a-2".to_string(), + amount: Uint128::new(100_000_000), + }, + ]), + ); + + let proposal_module = query_multiple_proposal_module(&app, &core_addr); + + let next_proposal_id: u64 = app + .wrap() + .query_wasm_smart(&proposal_module, &QueryMsg::NextProposalId {}) + .unwrap(); + assert_eq!(next_proposal_id, 1); + + let options = vec![ + MultipleChoiceOption { + description: "multiple choice option 1".to_string(), + msgs: vec![], + title: "title".to_string(), + }, + MultipleChoiceOption { + description: "multiple choice option 2".to_string(), + msgs: vec![], + title: "title".to_string(), + }, + ]; + let mc_options = MultipleChoiceOptions { options }; + + // Create a basic proposal with 2 options + app.execute_contract( + Addr::unchecked("a-1"), + proposal_module.clone(), + &ExecuteMsg::Propose { + title: "A simple text proposal".to_string(), + description: "A simple text proposal".to_string(), + choices: mc_options, + proposer: None, + }, + &[], + ) + .unwrap(); + + // veto from non open/passed/veto state should return an error + let err: ContractError = app + .execute_contract( + Addr::unchecked("vetoer"), + proposal_module.clone(), + &ExecuteMsg::Veto { proposal_id: 1 }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!( + err, + ContractError::VetoError(VetoError::NoVetoConfiguration {}) + ); +} + +#[test] +fn test_veto_open_prop_with_veto_before_passed_disabled() { + let mut app = App::default(); + let timelock_duration = 10; + let veto_config = VetoConfig { + timelock_duration: Duration::Height(timelock_duration), + vetoer: "vetoer".to_string(), + early_execute: false, + veto_before_passed: false, + }; + + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + InstantiateMsg { + min_voting_period: None, + max_voting_period: Duration::Height(6), + only_members_execute: false, + allow_revoting: false, + voting_strategy: VotingStrategy::SingleChoice { + quorum: PercentageThreshold::Majority {}, + }, + close_proposal_on_execution_failure: false, + pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: Some(veto_config), + }, + Some(vec![ + Cw20Coin { + address: "a-1".to_string(), + amount: Uint128::new(110_000_000), + }, + Cw20Coin { + address: "a-2".to_string(), + amount: Uint128::new(100_000_000), + }, + ]), + ); + let govmod = query_multiple_proposal_module(&app, &core_addr); + + let proposal_module = query_multiple_proposal_module(&app, &core_addr); + + let next_proposal_id: u64 = app + .wrap() + .query_wasm_smart(&proposal_module, &QueryMsg::NextProposalId {}) + .unwrap(); + assert_eq!(next_proposal_id, 1); + + let options = vec![ + MultipleChoiceOption { + description: "multiple choice option 1".to_string(), + msgs: vec![], + title: "title".to_string(), + }, + MultipleChoiceOption { + description: "multiple choice option 2".to_string(), + msgs: vec![], + title: "title".to_string(), + }, + ]; + let mc_options = MultipleChoiceOptions { options }; + + // Create a basic proposal with 2 options + app.execute_contract( + Addr::unchecked("a-1"), + proposal_module.clone(), + &ExecuteMsg::Propose { + title: "A simple text proposal".to_string(), + description: "A simple text proposal".to_string(), + choices: mc_options, + proposer: None, + }, + &[], + ) + .unwrap(); + + app.execute_contract( + Addr::unchecked("a-2"), + proposal_module.clone(), + &ExecuteMsg::Vote { + proposal_id: 1, + vote: MultipleChoiceVote { option_id: 0 }, + rationale: None, + }, + &[], + ) + .unwrap(); + + let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); + + assert_eq!(proposal.proposal.status, Status::Open {},); + + let err: ContractError = app + .execute_contract( + Addr::unchecked("vetoer"), + proposal_module.clone(), + &ExecuteMsg::Veto { proposal_id: 1 }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!( + err, + ContractError::VetoError(VetoError::NoVetoBeforePassed {}) + ); +} + +#[test] +fn test_veto_when_veto_timelock_expired() -> anyhow::Result<()> { + let mut app = App::default(); + let timelock_duration = Duration::Height(3); + let veto_config = VetoConfig { + timelock_duration, + vetoer: "vetoer".to_string(), + early_execute: false, + veto_before_passed: false, + }; + + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + InstantiateMsg { + min_voting_period: None, + max_voting_period: Duration::Height(6), + only_members_execute: false, + allow_revoting: false, + voting_strategy: VotingStrategy::SingleChoice { + quorum: PercentageThreshold::Majority {}, + }, + close_proposal_on_execution_failure: false, + pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: Some(veto_config), + }, + Some(vec![ + Cw20Coin { + address: "a-1".to_string(), + amount: Uint128::new(110_000_000), + }, + Cw20Coin { + address: "a-2".to_string(), + amount: Uint128::new(100_000_000), + }, + ]), + ); + let govmod = query_multiple_proposal_module(&app, &core_addr); + + let proposal_module = query_multiple_proposal_module(&app, &core_addr); + + let next_proposal_id: u64 = app + .wrap() + .query_wasm_smart(&proposal_module, &QueryMsg::NextProposalId {}) + .unwrap(); + assert_eq!(next_proposal_id, 1); + + let options = vec![ + MultipleChoiceOption { + description: "multiple choice option 1".to_string(), + msgs: vec![], + title: "title".to_string(), + }, + MultipleChoiceOption { + description: "multiple choice option 2".to_string(), + msgs: vec![], + title: "title".to_string(), + }, + ]; + let mc_options = MultipleChoiceOptions { options }; + + // Create a basic proposal with 2 options + app.execute_contract( + Addr::unchecked("a-1"), + proposal_module.clone(), + &ExecuteMsg::Propose { + title: "A simple text proposal".to_string(), + description: "A simple text proposal".to_string(), + choices: mc_options, + proposer: None, + }, + &[], + ) + .unwrap(); + + app.execute_contract( + Addr::unchecked("a-1"), + proposal_module.clone(), + &ExecuteMsg::Vote { + proposal_id: 1, + vote: MultipleChoiceVote { option_id: 0 }, + rationale: None, + }, + &[], + ) + .unwrap(); + + let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); + + assert_eq!( + proposal.proposal.status, + Status::VetoTimelock { + expiration: proposal.proposal.expiration.add(timelock_duration)?, + }, + ); + + // pass enough time to expire the timelock + app.update_block(|b| b.height += 10); + + let err: ContractError = app + .execute_contract( + Addr::unchecked("vetoer"), + proposal_module.clone(), + &ExecuteMsg::Veto { proposal_id: 1 }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!(err, ContractError::VetoError(VetoError::TimelockExpired {}),); + + Ok(()) +} + +#[test] +fn test_veto_sets_prop_status_to_vetoed() -> anyhow::Result<()> { + let mut app = App::default(); + let timelock_duration = Duration::Height(3); + let veto_config = VetoConfig { + timelock_duration, + vetoer: "vetoer".to_string(), + early_execute: false, + veto_before_passed: false, + }; + + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + InstantiateMsg { + min_voting_period: None, + max_voting_period: Duration::Height(6), + only_members_execute: false, + allow_revoting: false, + voting_strategy: VotingStrategy::SingleChoice { + quorum: PercentageThreshold::Majority {}, + }, + close_proposal_on_execution_failure: false, + pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: Some(veto_config), + }, + Some(vec![ + Cw20Coin { + address: "a-1".to_string(), + amount: Uint128::new(110_000_000), + }, + Cw20Coin { + address: "a-2".to_string(), + amount: Uint128::new(100_000_000), + }, + ]), + ); + let govmod = query_multiple_proposal_module(&app, &core_addr); + + let proposal_module = query_multiple_proposal_module(&app, &core_addr); + + let next_proposal_id: u64 = app + .wrap() + .query_wasm_smart(&proposal_module, &QueryMsg::NextProposalId {}) + .unwrap(); + assert_eq!(next_proposal_id, 1); + + let options = vec![ + MultipleChoiceOption { + description: "multiple choice option 1".to_string(), + msgs: vec![], + title: "title".to_string(), + }, + MultipleChoiceOption { + description: "multiple choice option 2".to_string(), + msgs: vec![], + title: "title".to_string(), + }, + ]; + let mc_options = MultipleChoiceOptions { options }; + + // Create a basic proposal with 2 options + app.execute_contract( + Addr::unchecked("a-1"), + proposal_module.clone(), + &ExecuteMsg::Propose { + title: "A simple text proposal".to_string(), + description: "A simple text proposal".to_string(), + choices: mc_options, + proposer: None, + }, + &[], + ) + .unwrap(); + + app.execute_contract( + Addr::unchecked("a-1"), + proposal_module.clone(), + &ExecuteMsg::Vote { + proposal_id: 1, + vote: MultipleChoiceVote { option_id: 0 }, + rationale: None, + }, + &[], + ) + .unwrap(); + + let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); + + assert_eq!( + proposal.proposal.status, + Status::VetoTimelock { + expiration: proposal.proposal.expiration.add(timelock_duration)?, + }, + ); + + app.execute_contract( + Addr::unchecked("vetoer"), + proposal_module.clone(), + &ExecuteMsg::Veto { proposal_id: 1 }, + &[], + ) + .unwrap(); + + let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); + + assert_eq!(proposal.proposal.status, Status::Vetoed {},); + + Ok(()) +} + +#[test] +fn test_veto_from_catchall_state() { + let mut app = App::default(); + let timelock_duration = 3; + let veto_config = VetoConfig { + timelock_duration: Duration::Height(timelock_duration), + vetoer: "vetoer".to_string(), + early_execute: true, + veto_before_passed: false, + }; + + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + InstantiateMsg { + min_voting_period: None, + max_voting_period: Duration::Height(6), + only_members_execute: false, + allow_revoting: false, + voting_strategy: VotingStrategy::SingleChoice { + quorum: PercentageThreshold::Majority {}, + }, + close_proposal_on_execution_failure: false, + pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: Some(veto_config), + }, + Some(vec![ + Cw20Coin { + address: "a-1".to_string(), + amount: Uint128::new(110_000_000), + }, + Cw20Coin { + address: "a-2".to_string(), + amount: Uint128::new(100_000_000), + }, + ]), + ); + let govmod = query_multiple_proposal_module(&app, &core_addr); + + let proposal_module = query_multiple_proposal_module(&app, &core_addr); + + let next_proposal_id: u64 = app + .wrap() + .query_wasm_smart(&proposal_module, &QueryMsg::NextProposalId {}) + .unwrap(); + assert_eq!(next_proposal_id, 1); + + let options = vec![ + MultipleChoiceOption { + description: "multiple choice option 1".to_string(), + msgs: vec![], + title: "title".to_string(), + }, + MultipleChoiceOption { + description: "multiple choice option 2".to_string(), + msgs: vec![], + title: "title".to_string(), + }, + ]; + let mc_options = MultipleChoiceOptions { options }; + + // Create a basic proposal with 2 options + app.execute_contract( + Addr::unchecked("a-1"), + proposal_module.clone(), + &ExecuteMsg::Propose { + title: "A simple text proposal".to_string(), + description: "A simple text proposal".to_string(), + choices: mc_options, + proposer: None, + }, + &[], + ) + .unwrap(); + + app.execute_contract( + Addr::unchecked("a-1"), + proposal_module.clone(), + &ExecuteMsg::Vote { + proposal_id: 1, + vote: MultipleChoiceVote { option_id: 0 }, + rationale: None, + }, + &[], + ) + .unwrap(); + + // pass enough time to expire the timelock + app.update_block(|b| b.height += 10); + + app.execute_contract( + Addr::unchecked("vetoer"), + proposal_module.clone(), + &ExecuteMsg::Execute { proposal_id: 1 }, + &[], + ) + .unwrap(); + + let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); + assert_eq!(proposal.proposal.status, Status::Executed {},); + + // veto from non open/passed/veto state should return an error + let err: ContractError = app + .execute_contract( + Addr::unchecked("vetoer"), + proposal_module.clone(), + &ExecuteMsg::Veto { proposal_id: 1 }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!( + err, + ContractError::VetoError(VetoError::InvalidProposalStatus { + status: "executed".to_string(), + }) + ); +} + +#[test] +fn test_veto_timelock_early_execute_happy() -> anyhow::Result<()> { + let mut app = App::default(); + let timelock_duration = Duration::Height(3); + let veto_config = VetoConfig { + timelock_duration, + vetoer: "vetoer".to_string(), + early_execute: true, + veto_before_passed: false, + }; + + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + InstantiateMsg { + min_voting_period: None, + max_voting_period: Duration::Height(6), + only_members_execute: true, + allow_revoting: false, + voting_strategy: VotingStrategy::SingleChoice { + quorum: PercentageThreshold::Majority {}, + }, + close_proposal_on_execution_failure: false, + pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: Some(veto_config), + }, + Some(vec![ + Cw20Coin { + address: "a-1".to_string(), + amount: Uint128::new(110_000_000), + }, + Cw20Coin { + address: "a-2".to_string(), + amount: Uint128::new(100_000_000), + }, + ]), + ); + let govmod = query_multiple_proposal_module(&app, &core_addr); + + let proposal_module = query_multiple_proposal_module(&app, &core_addr); + + let next_proposal_id: u64 = app + .wrap() + .query_wasm_smart(&proposal_module, &QueryMsg::NextProposalId {}) + .unwrap(); + assert_eq!(next_proposal_id, 1); + + let options = vec![ + MultipleChoiceOption { + description: "multiple choice option 1".to_string(), + msgs: vec![], + title: "title".to_string(), + }, + MultipleChoiceOption { + description: "multiple choice option 2".to_string(), + msgs: vec![], + title: "title".to_string(), + }, + ]; + let mc_options = MultipleChoiceOptions { options }; + + // Create a basic proposal with 2 options + app.execute_contract( + Addr::unchecked("a-1"), + proposal_module.clone(), + &ExecuteMsg::Propose { + title: "A simple text proposal".to_string(), + description: "A simple text proposal".to_string(), + choices: mc_options, + proposer: None, + }, + &[], + ) + .unwrap(); + + app.execute_contract( + Addr::unchecked("a-1"), + proposal_module.clone(), + &ExecuteMsg::Vote { + proposal_id: 1, + vote: MultipleChoiceVote { option_id: 0 }, + rationale: None, + }, + &[], + ) + .unwrap(); + + let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); + + assert_eq!( + proposal.proposal.status, + Status::VetoTimelock { + expiration: proposal.proposal.expiration.add(timelock_duration)?, + }, + ); + + // first we try unauthorized early execution + let err: ContractError = app + .execute_contract( + Addr::unchecked("not-the-vetoer"), + proposal_module.clone(), + &ExecuteMsg::Execute { proposal_id: 1 }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!(err, ContractError::Unauthorized {}); + + app.execute_contract( + Addr::unchecked("vetoer"), + proposal_module.clone(), + &ExecuteMsg::Execute { proposal_id: 1 }, + &[], + ) + .unwrap(); + + let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); + assert_eq!(proposal.proposal.status, Status::Executed {},); + + Ok(()) +} + +#[test] +fn test_veto_timelock_expires_happy() -> anyhow::Result<()> { + let mut app = App::default(); + let timelock_duration = Duration::Height(3); + let veto_config = VetoConfig { + timelock_duration, + vetoer: "vetoer".to_string(), + early_execute: false, + veto_before_passed: false, + }; + + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + InstantiateMsg { + min_voting_period: None, + max_voting_period: Duration::Height(6), + only_members_execute: false, + allow_revoting: false, + voting_strategy: VotingStrategy::SingleChoice { + quorum: PercentageThreshold::Majority {}, + }, + close_proposal_on_execution_failure: false, + pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: Some(veto_config), + }, + Some(vec![ + Cw20Coin { + address: "a-1".to_string(), + amount: Uint128::new(110_000_000), + }, + Cw20Coin { + address: "a-2".to_string(), + amount: Uint128::new(100_000_000), + }, + ]), + ); + let govmod = query_multiple_proposal_module(&app, &core_addr); + + let proposal_module = query_multiple_proposal_module(&app, &core_addr); + + let next_proposal_id: u64 = app + .wrap() + .query_wasm_smart(&proposal_module, &QueryMsg::NextProposalId {}) + .unwrap(); + assert_eq!(next_proposal_id, 1); + + let options = vec![ + MultipleChoiceOption { + description: "multiple choice option 1".to_string(), + msgs: vec![], + title: "title".to_string(), + }, + MultipleChoiceOption { + description: "multiple choice option 2".to_string(), + msgs: vec![], + title: "title".to_string(), + }, + ]; + let mc_options = MultipleChoiceOptions { options }; + + // Create a basic proposal with 2 options + app.execute_contract( + Addr::unchecked("a-1"), + proposal_module.clone(), + &ExecuteMsg::Propose { + title: "A simple text proposal".to_string(), + description: "A simple text proposal".to_string(), + choices: mc_options, + proposer: None, + }, + &[], + ) + .unwrap(); + + app.execute_contract( + Addr::unchecked("a-1"), + proposal_module.clone(), + &ExecuteMsg::Vote { + proposal_id: 1, + vote: MultipleChoiceVote { option_id: 0 }, + rationale: None, + }, + &[], + ) + .unwrap(); + + let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); + + assert_eq!( + proposal.proposal.status, + Status::VetoTimelock { + expiration: proposal.proposal.expiration.add(timelock_duration)?, + }, + ); + + // pass enough time to expire the timelock + app.update_block(|b| b.height += 10); + + app.execute_contract( + Addr::unchecked("a-1"), + proposal_module.clone(), + &ExecuteMsg::Execute { proposal_id: 1 }, + &[], + ) + .unwrap(); + + let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); + assert_eq!(proposal.proposal.status, Status::Executed {},); + + Ok(()) +} diff --git a/contracts/proposal/dao-proposal-single/Cargo.toml b/contracts/proposal/dao-proposal-single/Cargo.toml index eb23de7f3..af3d872ce 100644 --- a/contracts/proposal/dao-proposal-single/Cargo.toml +++ b/contracts/proposal/dao-proposal-single/Cargo.toml @@ -39,6 +39,7 @@ voting-v1 = { workspace = true } cw-proposal-single-v1 = { workspace = true, features = ["library"] } [dev-dependencies] +anyhow = { workspace = true } cosmwasm-schema = { workspace = true } cw-multi-test = { workspace = true } dao-dao-core = { workspace = true } diff --git a/contracts/proposal/dao-proposal-single/README.md b/contracts/proposal/dao-proposal-single/README.md index 204631f22..1eb27dc30 100644 --- a/contracts/proposal/dao-proposal-single/README.md +++ b/contracts/proposal/dao-proposal-single/README.md @@ -59,3 +59,55 @@ handling a hook. The proposals may be configured to allow revoting. In such cases, users are able to change their vote as long as the proposal is still open. Revoting for the currently cast option will return an error. + +## Veto + +Proposals may be configured with an optional `VetoConfig` - a configuration describing +the veto flow. + +VetoConfig timelock period enables a party (such as an oversight committee DAO) +to hold the main DAO accountable by vetoing proposals once (and potentially +before) they are passed for a given timelock period. + +No actions from DAO members are allowed during the timelock period. + +After the timelock expires, the proposal can be executed normally. + +`VetoConfig` contains the following fields: + +### `timelock_duration` + +Timelock duration (`cw_utils::Duration`) describes the duration of timelock +in blocks or seconds. + +The delay duration is added to the proposal's expiration to get the timelock +expiration (`Expiration`) used for the new proposal state of `VetoTimelock { +expiration: Expiration }`. + +If the vetoer address is another DAO, this duration should be carefully +considered based on of the vetoer DAO's voting period. + +### `vetoer` + +Vetoer (`String`) is the address of the account allowed to veto the proposals +that are in `VetoTimelock` state. + +Vetoer address can be updated via a regular proposal config update. + +If you want the `vetoer` role to be shared between multiple organizations or +individuals, a +[cw1-whitelist](https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw1-whitelist) +contract address can be used to allow multiple accounts to veto the prop. + +### `early_execute` + +Early execute (`bool`) is a flag used to indicate whether the vetoer can execute +the proposals before the timelock period is expired. The proposals still need to +be passed and in the `VetoTimelock` state in order for this to be possible. This +may prevent the veto flow from consistently lengthening the governance process. + +### `veto_before_passed` + +Veto before passed (`bool`) is a flag used to indicate whether the vetoer +can veto a proposal before it passes. Votes may still be cast until the +specified proposal expiration, even once vetoed. diff --git a/contracts/proposal/dao-proposal-single/schema/dao-proposal-single.json b/contracts/proposal/dao-proposal-single/schema/dao-proposal-single.json index 465e84815..6b2c48888 100644 --- a/contracts/proposal/dao-proposal-single/schema/dao-proposal-single.json +++ b/contracts/proposal/dao-proposal-single/schema/dao-proposal-single.json @@ -61,6 +61,17 @@ "$ref": "#/definitions/Threshold" } ] + }, + "veto": { + "description": "Optional veto configuration for proposal execution. If set, proposals can only be executed after the timelock delay expiration. During this period an oversight account (`veto.vetoer`) can veto the proposal.", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false, @@ -362,6 +373,38 @@ "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" + }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to lock a proposal for after its expiration to allow the vetoer to veto.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false } } }, @@ -478,6 +521,31 @@ }, "additionalProperties": false }, + { + "description": "Callable only if veto is configured", + "type": "object", + "required": [ + "veto" + ], + "properties": { + "veto": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "description": "The ID of the proposal to veto.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Closes a proposal that has failed (either not passed or timed out). If applicable this will cause the proposal deposit associated wth said proposal to be returned.", "type": "object", @@ -563,6 +631,17 @@ "$ref": "#/definitions/Threshold" } ] + }, + "veto": { + "description": "Optional time delay on proposal execution, during which the proposal may be vetoed.", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false @@ -1523,6 +1602,38 @@ "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", "type": "string" }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to lock a proposal for after its expiration to allow the vetoer to veto.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false + }, "Vote": { "oneOf": [ { @@ -2041,6 +2152,17 @@ "$ref": "#/definitions/PreProposeInfo" } ] + }, + "veto": { + "description": "This field was not present in DAO DAO v1. To migrate, a value must be specified.\n\noptional configuration for veto feature", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false @@ -2123,6 +2245,40 @@ } } }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, "ModuleInstantiateInfo": { "description": "Information needed to instantiate a module.", "type": "object", @@ -2215,6 +2371,38 @@ "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" + }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to lock a proposal for after its expiration to allow the vetoer to veto.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false } } }, @@ -2280,6 +2468,17 @@ "$ref": "#/definitions/Threshold" } ] + }, + "veto": { + "description": "Optional veto configuration. If set to `None`, veto option is disabled. Otherwise contains the configuration for veto flow.", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false, @@ -2436,6 +2635,38 @@ "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" + }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to lock a proposal for after its expiration to allow the vetoer to veto.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false } } }, @@ -2841,6 +3072,40 @@ } ] }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, "Empty": { "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", "type": "object" @@ -3151,9 +3416,11 @@ ], "properties": { "allow_revoting": { + "description": "Whether or not revoting is enabled. If revoting is enabled, a proposal cannot pass until the voting period has elapsed.", "type": "boolean" }, "description": { + "description": "The main body of the proposal text", "type": "string" }, "expiration": { @@ -3197,7 +3464,12 @@ "minimum": 0.0 }, "status": { - "$ref": "#/definitions/Status" + "description": "The proposal status", + "allOf": [ + { + "$ref": "#/definitions/Status" + } + ] }, "threshold": { "description": "The threshold at which this proposal will pass.", @@ -3208,6 +3480,7 @@ ] }, "title": { + "description": "The title of the proposal", "type": "string" }, "total_power": { @@ -3218,8 +3491,24 @@ } ] }, + "veto": { + "description": "Optional veto configuration. If set to `None`, veto option is disabled. Otherwise contains the configuration for veto flow.", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] + }, "votes": { - "$ref": "#/definitions/Votes" + "description": "Votes on a particular proposal", + "allOf": [ + { + "$ref": "#/definitions/Votes" + } + ] } }, "additionalProperties": false @@ -3351,6 +3640,35 @@ "enum": [ "execution_failed" ] + }, + { + "description": "The proposal is timelocked. Only the configured vetoer can execute or veto until the timelock expires.", + "type": "object", + "required": [ + "veto_timelock" + ], + "properties": { + "veto_timelock": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "The proposal has been vetoed.", + "type": "string", + "enum": [ + "vetoed" + ] } ] }, @@ -3445,6 +3763,38 @@ "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", "type": "string" }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to lock a proposal for after its expiration to allow the vetoer to veto.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false + }, "VoteOption": { "type": "string", "enum": [ @@ -4022,6 +4372,40 @@ } ] }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, "Empty": { "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", "type": "object" @@ -4312,9 +4696,11 @@ ], "properties": { "allow_revoting": { + "description": "Whether or not revoting is enabled. If revoting is enabled, a proposal cannot pass until the voting period has elapsed.", "type": "boolean" }, "description": { + "description": "The main body of the proposal text", "type": "string" }, "expiration": { @@ -4358,7 +4744,12 @@ "minimum": 0.0 }, "status": { - "$ref": "#/definitions/Status" + "description": "The proposal status", + "allOf": [ + { + "$ref": "#/definitions/Status" + } + ] }, "threshold": { "description": "The threshold at which this proposal will pass.", @@ -4369,6 +4760,7 @@ ] }, "title": { + "description": "The title of the proposal", "type": "string" }, "total_power": { @@ -4379,8 +4771,24 @@ } ] }, + "veto": { + "description": "Optional veto configuration. If set to `None`, veto option is disabled. Otherwise contains the configuration for veto flow.", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] + }, "votes": { - "$ref": "#/definitions/Votes" + "description": "Votes on a particular proposal", + "allOf": [ + { + "$ref": "#/definitions/Votes" + } + ] } }, "additionalProperties": false @@ -4512,6 +4920,35 @@ "enum": [ "execution_failed" ] + }, + { + "description": "The proposal is timelocked. Only the configured vetoer can execute or veto until the timelock expires.", + "type": "object", + "required": [ + "veto_timelock" + ], + "properties": { + "veto_timelock": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "The proposal has been vetoed.", + "type": "string", + "enum": [ + "vetoed" + ] } ] }, @@ -4606,6 +5043,38 @@ "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", "type": "string" }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to lock a proposal for after its expiration to allow the vetoer to veto.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false + }, "VoteOption": { "type": "string", "enum": [ @@ -5148,6 +5617,40 @@ } ] }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, "Empty": { "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", "type": "object" @@ -5458,9 +5961,11 @@ ], "properties": { "allow_revoting": { + "description": "Whether or not revoting is enabled. If revoting is enabled, a proposal cannot pass until the voting period has elapsed.", "type": "boolean" }, "description": { + "description": "The main body of the proposal text", "type": "string" }, "expiration": { @@ -5504,7 +6009,12 @@ "minimum": 0.0 }, "status": { - "$ref": "#/definitions/Status" + "description": "The proposal status", + "allOf": [ + { + "$ref": "#/definitions/Status" + } + ] }, "threshold": { "description": "The threshold at which this proposal will pass.", @@ -5515,6 +6025,7 @@ ] }, "title": { + "description": "The title of the proposal", "type": "string" }, "total_power": { @@ -5525,8 +6036,24 @@ } ] }, + "veto": { + "description": "Optional veto configuration. If set to `None`, veto option is disabled. Otherwise contains the configuration for veto flow.", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] + }, "votes": { - "$ref": "#/definitions/Votes" + "description": "Votes on a particular proposal", + "allOf": [ + { + "$ref": "#/definitions/Votes" + } + ] } }, "additionalProperties": false @@ -5658,6 +6185,35 @@ "enum": [ "execution_failed" ] + }, + { + "description": "The proposal is timelocked. Only the configured vetoer can execute or veto until the timelock expires.", + "type": "object", + "required": [ + "veto_timelock" + ], + "properties": { + "veto_timelock": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "The proposal has been vetoed.", + "type": "string", + "enum": [ + "vetoed" + ] } ] }, @@ -5752,6 +6308,38 @@ "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", "type": "string" }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to lock a proposal for after its expiration to allow the vetoer to veto.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false + }, "VoteOption": { "type": "string", "enum": [ diff --git a/contracts/proposal/dao-proposal-single/src/contract.rs b/contracts/proposal/dao-proposal-single/src/contract.rs index 93e73eb76..d9ac7c07f 100644 --- a/contracts/proposal/dao-proposal-single/src/contract.rs +++ b/contracts/proposal/dao-proposal-single/src/contract.rs @@ -6,10 +6,11 @@ use cosmwasm_std::{ }; use cw2::{get_contract_version, set_contract_version, ContractVersion}; use cw_hooks::Hooks; -use cw_proposal_single_v1 as v1; use cw_storage_plus::Bound; use cw_utils::{parse_reply_instantiate_data, Duration}; -use dao_hooks::proposal::{new_proposal_hooks, proposal_status_changed_hooks}; +use dao_hooks::proposal::{ + new_proposal_hooks, proposal_completed_hooks, proposal_status_changed_hooks, +}; use dao_hooks::vote::new_vote_hooks; use dao_interface::voting::IsActiveResponse; use dao_voting::pre_propose::{PreProposeInfo, ProposalCreationPolicy}; @@ -21,12 +22,12 @@ use dao_voting::reply::{ }; use dao_voting::status::Status; use dao_voting::threshold::Threshold; +use dao_voting::veto::{VetoConfig, VetoError}; use dao_voting::voting::{get_total_power, get_voting_power, validate_voting_period, Vote, Votes}; use crate::msg::MigrateMsg; use crate::proposal::{next_proposal_id, SingleChoiceProposal}; use crate::state::{Config, CREATION_POLICY}; - use crate::v1_state::{ v1_duration_to_v2, v1_expiration_to_v2, v1_status_to_v2, v1_threshold_to_v2, v1_votes_to_v2, }; @@ -38,14 +39,10 @@ use crate::{ query::{ProposalResponse, VoteInfo, VoteListResponse, VoteResponse}, state::{Ballot, BALLOTS, CONFIG, PROPOSALS, PROPOSAL_COUNT, PROPOSAL_HOOKS, VOTE_HOOKS}, }; - +use cw_proposal_single_v1 as v1; pub(crate) const CONTRACT_NAME: &str = "crates.io:dao-proposal-single"; pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -/// Message type used for firing hooks to this module's pre-propose -/// module, if one is installed. -type PreProposeHookMsg = dao_pre_propose_base::msg::ExecuteMsg; - #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, @@ -66,6 +63,11 @@ pub fn instantiate( .pre_propose_info .into_initial_policy_and_messages(dao.clone())?; + // if veto is configured, validate its fields + if let Some(veto_config) = &msg.veto { + veto_config.validate(&deps.as_ref(), &max_voting_period)?; + }; + let config = Config { threshold: msg.threshold, max_voting_period, @@ -74,6 +76,7 @@ pub fn instantiate( dao: dao.clone(), allow_revoting: msg.allow_revoting, close_proposal_on_execution_failure: msg.close_proposal_on_execution_failure, + veto: msg.veto, }; // Initialize proposal count to zero so that queries return zero @@ -121,6 +124,7 @@ pub fn execute( allow_revoting, dao, close_proposal_on_execution_failure, + veto, } => execute_update_config( deps, info, @@ -131,6 +135,7 @@ pub fn execute( allow_revoting, dao, close_proposal_on_execution_failure, + veto, ), ExecuteMsg::UpdatePreProposeInfo { info: new_info } => { execute_update_proposal_creation_policy(deps, info, new_info) @@ -145,6 +150,7 @@ pub fn execute( ExecuteMsg::RemoveVoteHook { address } => { execute_remove_vote_hook(deps, env, info, address) } + ExecuteMsg::Veto { proposal_id } => execute_veto(deps, env, info, proposal_id), } } @@ -213,10 +219,11 @@ pub fn execute_propose( status: Status::Open, votes: Votes::zero(), allow_revoting: config.allow_revoting, + veto: config.veto, }; // Update the proposal's status. Addresses case where proposal // expires on the same block as it is created. - proposal.update_status(&env.block); + proposal.update_status(&env.block)?; proposal }; let id = advance_proposal_id(deps.storage)?; @@ -255,6 +262,79 @@ pub fn execute_propose( .add_attribute("status", proposal.status.to_string())) } +pub fn execute_veto( + deps: DepsMut, + env: Env, + info: MessageInfo, + proposal_id: u64, +) -> Result { + let mut prop = PROPOSALS + .may_load(deps.storage, proposal_id)? + .ok_or(ContractError::NoSuchProposal { id: proposal_id })?; + + // ensure status is up to date + prop.update_status(&env.block)?; + let old_status = prop.status; + + let veto_config = prop + .veto + .as_ref() + .ok_or(VetoError::NoVetoConfiguration {})?; + + // Check sender is vetoer + veto_config.check_is_vetoer(&info)?; + + match prop.status { + Status::Open => { + // can only veto an open proposal if veto_before_passed is enabled. + veto_config.check_veto_before_passed_enabled()?; + } + Status::Passed => { + // if this proposal has veto configured but is in the passed state, + // the timelock already expired, so provide a more specific error. + return Err(ContractError::VetoError(VetoError::TimelockExpired {})); + } + Status::VetoTimelock { expiration } => { + // vetoer can veto the proposal iff the timelock is active/not + // expired. this should never happen since the status updates to + // passed after the timelock expires, but let's check anyway. + if expiration.is_expired(&env.block) { + return Err(ContractError::VetoError(VetoError::TimelockExpired {})); + } + } + // generic status error if the proposal has any other status. + _ => { + return Err(ContractError::VetoError(VetoError::InvalidProposalStatus { + status: prop.status.to_string(), + })); + } + } + + // Update proposal status to vetoed + prop.status = Status::Vetoed; + PROPOSALS.save(deps.storage, proposal_id, &prop)?; + + // Add proposal status change hooks + let proposal_status_changed_hooks = proposal_status_changed_hooks( + PROPOSAL_HOOKS, + deps.storage, + proposal_id, + old_status.to_string(), + prop.status.to_string(), + )?; + + // Add prepropose / deposit module hook which will handle deposit refunds. + let proposal_creation_policy = CREATION_POLICY.load(deps.storage)?; + let proposal_completed_hooks = + proposal_completed_hooks(proposal_creation_policy, proposal_id, prop.status)?; + + Ok(Response::new() + .add_attribute("action", "veto") + .add_attribute("proposal_id", proposal_id.to_string()) + .add_submessages(proposal_status_changed_hooks) + .add_submessages(proposal_completed_hooks)) +} + pub fn execute_execute( deps: DepsMut, env: Env, @@ -273,18 +353,52 @@ pub fn execute_execute( &config.dao, Some(prop.start_height), )?; - if power.is_zero() { + + // if there is no veto config, then caller is not the vetoer + // if there is, we validate the caller addr + let vetoer_call = prop + .veto + .as_ref() + .map_or(false, |veto_config| veto_config.vetoer == info.sender); + + if power.is_zero() && !vetoer_call { return Err(ContractError::Unauthorized {}); } } - // Check here that the proposal is passed. Allow it to be executed - // even if it is expired so long as it passed during its voting - // period. + // Check here that the proposal is passed or timelocked. + // Allow it to be executed even if it is expired so long + // as it passed during its voting period. Allow it to be + // executed in timelock state if early_execute is enabled + // and the sender is the vetoer. + prop.update_status(&env.block)?; let old_status = prop.status; - prop.update_status(&env.block); - if prop.status != Status::Passed { - return Err(ContractError::NotPassed {}); + match &prop.status { + Status::Passed => (), + Status::VetoTimelock { expiration } => { + let veto_config = prop + .veto + .as_ref() + .ok_or(VetoError::NoVetoConfiguration {})?; + + // Check if the sender is the vetoer + match veto_config.vetoer == info.sender { + // if sender is the vetoer we validate the early exec flag + true => veto_config.check_early_execute_enabled()?, + // otherwise timelock must be expired in order to execute + false => { + // it should never be expired here since the status updates + // to passed after the timelock expires, but let's check + // anyway. i.e. this error should always be returned. + if !expiration.is_expired(&env.block) { + return Err(ContractError::VetoError(VetoError::Timelocked {})); + } + } + } + } + _ => { + return Err(ContractError::NotPassed {}); + } } prop.status = Status::Executed; @@ -313,7 +427,8 @@ pub fn execute_execute( } }; - let hooks = proposal_status_changed_hooks( + // Add proposal status change hooks + let proposal_status_changed_hooks = proposal_status_changed_hooks( PROPOSAL_HOOKS, deps.storage, proposal_id, @@ -323,28 +438,12 @@ pub fn execute_execute( // Add prepropose / deposit module hook which will handle deposit refunds. let proposal_creation_policy = CREATION_POLICY.load(deps.storage)?; - let hooks = match proposal_creation_policy { - ProposalCreationPolicy::Anyone {} => hooks, - ProposalCreationPolicy::Module { addr } => { - let msg = to_json_binary(&PreProposeHookMsg::ProposalCompletedHook { - proposal_id, - new_status: prop.status, - })?; - let mut hooks = hooks; - hooks.push(SubMsg::reply_on_error( - WasmMsg::Execute { - contract_addr: addr.into_string(), - msg, - funds: vec![], - }, - failed_pre_propose_module_hook_id(), - )); - hooks - } - }; + let proposal_completed_hooks = + proposal_completed_hooks(proposal_creation_policy, proposal_id, prop.status)?; Ok(response - .add_submessages(hooks) + .add_submessages(proposal_status_changed_hooks) + .add_submessages(proposal_completed_hooks) .add_attribute("action", "execute") .add_attribute("sender", info.sender) .add_attribute("proposal_id", proposal_id.to_string()) @@ -420,7 +519,7 @@ pub fn execute_vote( let old_status = prop.status; prop.votes.add_vote(vote, vote_power); - prop.update_status(&env.block); + prop.update_status(&env.block)?; PROPOSALS.save(deps.storage, proposal_id, &prop)?; @@ -492,7 +591,7 @@ pub fn execute_close( // Update status to ensure that proposals which were open and have // expired are moved to "rejected." - prop.update_status(&env.block); + prop.update_status(&env.block)?; if prop.status != Status::Rejected { return Err(ContractError::WrongCloseStatus {}); } @@ -502,7 +601,8 @@ pub fn execute_close( prop.status = Status::Closed; PROPOSALS.save(deps.storage, proposal_id, &prop)?; - let hooks = proposal_status_changed_hooks( + // Add proposal status change hooks + let proposal_status_changed_hooks = proposal_status_changed_hooks( PROPOSAL_HOOKS, deps.storage, proposal_id, @@ -512,28 +612,12 @@ pub fn execute_close( // Add prepropose / deposit module hook which will handle deposit refunds. let proposal_creation_policy = CREATION_POLICY.load(deps.storage)?; - let hooks = match proposal_creation_policy { - ProposalCreationPolicy::Anyone {} => hooks, - ProposalCreationPolicy::Module { addr } => { - let msg = to_json_binary(&PreProposeHookMsg::ProposalCompletedHook { - proposal_id, - new_status: prop.status, - })?; - let mut hooks = hooks; - hooks.push(SubMsg::reply_on_error( - WasmMsg::Execute { - contract_addr: addr.into_string(), - msg, - funds: vec![], - }, - failed_pre_propose_module_hook_id(), - )); - hooks - } - }; + let proposal_completed_hooks = + proposal_completed_hooks(proposal_creation_policy, proposal_id, prop.status)?; Ok(Response::default() - .add_submessages(hooks) + .add_submessages(proposal_status_changed_hooks) + .add_submessages(proposal_completed_hooks) .add_attribute("action", "close") .add_attribute("sender", info.sender) .add_attribute("proposal_id", proposal_id.to_string())) @@ -550,6 +634,7 @@ pub fn execute_update_config( allow_revoting: bool, dao: String, close_proposal_on_execution_failure: bool, + veto: Option, ) -> Result { let config = CONFIG.load(deps.storage)?; @@ -563,6 +648,11 @@ pub fn execute_update_config( let (min_voting_period, max_voting_period) = validate_voting_period(min_voting_period, max_voting_period)?; + // if veto is configured, validate its fields + if let Some(veto_config) = &veto { + veto_config.validate(&deps.as_ref(), &max_voting_period)?; + }; + CONFIG.save( deps.storage, &Config { @@ -573,6 +663,7 @@ pub fn execute_update_config( allow_revoting, dao, close_proposal_on_execution_failure, + veto, }, )?; @@ -747,7 +838,7 @@ pub fn query_dao(deps: Deps) -> StdResult { pub fn query_proposal(deps: Deps, env: Env, id: u64) -> StdResult { let proposal = PROPOSALS.load(deps.storage, id)?; - to_json_binary(&proposal.into_response(&env.block, id)) + to_json_binary(&proposal.into_response(&env.block, id)?) } pub fn query_creation_policy(deps: Deps) -> StdResult { @@ -769,7 +860,7 @@ pub fn query_list_proposals( .collect::, _>>()? .into_iter() .map(|(id, proposal)| proposal.into_response(&env.block, id)) - .collect(); + .collect::>>()?; to_json_binary(&ProposalListResponse { proposals: props }) } @@ -788,7 +879,7 @@ pub fn query_reverse_proposals( .collect::, _>>()? .into_iter() .map(|(id, proposal)| proposal.into_response(&env.block, id)) - .collect(); + .collect::>>()?; to_json_binary(&ProposalListResponse { proposals: props }) } @@ -828,7 +919,7 @@ pub fn query_list_votes( let votes = BALLOTS .prefix(proposal_id) - .range(deps.storage, min, None, cosmwasm_std::Order::Ascending) + .range(deps.storage, min, None, Order::Ascending) .take(limit as usize) .map(|item| { let (voter, ballot) = item?; @@ -858,6 +949,7 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { // `CONTRACT_VERSION` here is from the data section of the // blob we are migrating to. `version` is from storage. If @@ -867,19 +959,27 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result Result Result Ok(Response::default() .add_attribute("action", "migrate") .add_attribute("from", "compatible")), diff --git a/contracts/proposal/dao-proposal-single/src/error.rs b/contracts/proposal/dao-proposal-single/src/error.rs index 8af18058f..9fc049d21 100644 --- a/contracts/proposal/dao-proposal-single/src/error.rs +++ b/contracts/proposal/dao-proposal-single/src/error.rs @@ -3,10 +3,10 @@ use std::u64; use cosmwasm_std::StdError; use cw_hooks::HookError; use cw_utils::ParseReplyError; -use dao_voting::reply::error::TagError; +use dao_voting::{reply::error::TagError, veto::VetoError}; use thiserror::Error; -#[derive(Error, Debug)] +#[derive(Error, Debug, PartialEq)] pub enum ContractError { #[error(transparent)] Std(#[from] StdError), @@ -17,6 +17,9 @@ pub enum ContractError { #[error(transparent)] HookError(#[from] HookError), + #[error(transparent)] + VetoError(#[from] VetoError), + #[error("unauthorized")] Unauthorized {}, @@ -89,4 +92,7 @@ pub enum ContractError { #[error("can not migrate. current version is up to date")] AlreadyMigrated {}, + + #[error("incompatible migration version")] + MigrationVersionError {}, } diff --git a/contracts/proposal/dao-proposal-single/src/lib.rs b/contracts/proposal/dao-proposal-single/src/lib.rs index c9076cf76..b556f8b06 100644 --- a/contracts/proposal/dao-proposal-single/src/lib.rs +++ b/contracts/proposal/dao-proposal-single/src/lib.rs @@ -10,6 +10,6 @@ pub mod query; mod testing; pub mod state; -mod v1_state; +pub mod v1_state; pub use crate::error::ContractError; diff --git a/contracts/proposal/dao-proposal-single/src/msg.rs b/contracts/proposal/dao-proposal-single/src/msg.rs index c0be9b317..302303b48 100644 --- a/contracts/proposal/dao-proposal-single/src/msg.rs +++ b/contracts/proposal/dao-proposal-single/src/msg.rs @@ -3,7 +3,7 @@ use cw_utils::Duration; use dao_dao_macros::proposal_module_query; use dao_voting::{ pre_propose::PreProposeInfo, proposal::SingleChoiceProposeMsg, threshold::Threshold, - voting::Vote, + veto::VetoConfig, voting::Vote, }; #[cw_serde] @@ -38,6 +38,12 @@ pub struct InstantiateMsg { /// remain open until the DAO's treasury was large enough for it to be /// executed. pub close_proposal_on_execution_failure: bool, + /// Optional veto configuration for proposal execution. + /// If set, proposals can only be executed after the timelock + /// delay expiration. + /// During this period an oversight account (`veto.vetoer`) can + /// veto the proposal. + pub veto: Option, } #[cw_serde] @@ -68,6 +74,11 @@ pub enum ExecuteMsg { /// The ID of the proposal to execute. proposal_id: u64, }, + /// Callable only if veto is configured + Veto { + /// The ID of the proposal to veto. + proposal_id: u64, + }, /// Closes a proposal that has failed (either not passed or timed /// out). If applicable this will cause the proposal deposit /// associated wth said proposal to be returned. @@ -110,6 +121,9 @@ pub enum ExecuteMsg { /// remain open until the DAO's treasury was large enough for it to be /// executed. close_proposal_on_execution_failure: bool, + /// Optional time delay on proposal execution, during which the + /// proposal may be vetoed. + veto: Option, }, /// Update's the proposal creation policy used for this /// module. Only the DAO may call this method. @@ -219,6 +233,11 @@ pub enum MigrateMsg { /// no deposit or membership checks when submitting a proposal. The "ModuleMayPropose" /// option allows for instantiating a prepropose module which will handle deposit verification and return logic. pre_propose_info: PreProposeInfo, + /// This field was not present in DAO DAO v1. To migrate, a + /// value must be specified. + /// + /// optional configuration for veto feature + veto: Option, }, FromCompatible {}, } diff --git a/contracts/proposal/dao-proposal-single/src/proposal.rs b/contracts/proposal/dao-proposal-single/src/proposal.rs index 9ba174ad5..a597f3754 100644 --- a/contracts/proposal/dao-proposal-single/src/proposal.rs +++ b/contracts/proposal/dao-proposal-single/src/proposal.rs @@ -1,3 +1,5 @@ +use std::ops::Add; + use crate::query::ProposalResponse; use crate::state::PROPOSAL_COUNT; use cosmwasm_schema::cw_serde; @@ -5,11 +7,14 @@ use cosmwasm_std::{Addr, BlockInfo, CosmosMsg, Decimal, Empty, StdResult, Storag use cw_utils::Expiration; use dao_voting::status::Status; use dao_voting::threshold::{PercentageThreshold, Threshold}; +use dao_voting::veto::VetoConfig; use dao_voting::voting::{does_vote_count_fail, does_vote_count_pass, Votes}; #[cw_serde] pub struct SingleChoiceProposal { + /// The title of the proposal pub title: String, + /// The main body of the proposal text pub description: String, /// The address that created this proposal. pub proposer: Addr, @@ -31,9 +36,16 @@ pub struct SingleChoiceProposal { pub total_power: Uint128, /// The messages that will be executed should this proposal pass. pub msgs: Vec>, + /// The proposal status pub status: Status, + /// Votes on a particular proposal pub votes: Votes, + /// Whether or not revoting is enabled. If revoting is enabled, a proposal + /// cannot pass until the voting period has elapsed. pub allow_revoting: bool, + /// Optional veto configuration. If set to `None`, veto option + /// is disabled. Otherwise contains the configuration for veto flow. + pub veto: Option, } pub fn next_proposal_id(store: &dyn Storage) -> StdResult { @@ -54,28 +66,50 @@ impl SingleChoiceProposal { /// a vote has occurred, the status we read from the proposal status /// may be out of date. This method recomputes the status so that /// queries get accurate information. - pub fn into_response(mut self, block: &BlockInfo, id: u64) -> ProposalResponse { - self.update_status(block); - ProposalResponse { id, proposal: self } + pub fn into_response(mut self, block: &BlockInfo, id: u64) -> StdResult { + self.update_status(block)?; + Ok(ProposalResponse { id, proposal: self }) } /// Gets the current status of the proposal. - pub fn current_status(&self, block: &BlockInfo) -> Status { - if self.status == Status::Open && self.is_passed(block) { - Status::Passed - } else if self.status == Status::Open - && (self.expiration.is_expired(block) || self.is_rejected(block)) - { - Status::Rejected - } else { - self.status + pub fn current_status(&self, block: &BlockInfo) -> StdResult { + match self.status { + Status::Open if self.is_passed(block) => match &self.veto { + // if prop is passed and veto is configured, calculate timelock + // expiration. if it's expired, this proposal has passed. + // otherwise, set status to `VetoTimelock`. + Some(veto_config) => { + let expiration = self.expiration.add(veto_config.timelock_duration)?; + + if expiration.is_expired(block) { + Ok(Status::Passed) + } else { + Ok(Status::VetoTimelock { expiration }) + } + } + // Otherwise the proposal is simply passed + None => Ok(Status::Passed), + }, + Status::Open if self.expiration.is_expired(block) || self.is_rejected(block) => { + Ok(Status::Rejected) + } + Status::VetoTimelock { expiration } => { + // if prop timelock expired, proposal is now passed. + if expiration.is_expired(block) { + Ok(Status::Passed) + } else { + Ok(self.status) + } + } + _ => Ok(self.status), } } /// Sets a proposals status to its current status. - pub fn update_status(&mut self, block: &BlockInfo) { - let new_status = self.current_status(block); - self.status = new_status + pub fn update_status(&mut self, block: &BlockInfo) -> StdResult<()> { + let new_status = self.current_status(block)?; + self.status = new_status; + Ok(()) } /// Returns true iff this proposal is sure to pass (even before @@ -275,6 +309,7 @@ mod test { msgs: vec![], status: Status::Open, threshold, + veto: None, total_power, votes, }; diff --git a/contracts/proposal/dao-proposal-single/src/state.rs b/contracts/proposal/dao-proposal-single/src/state.rs index 3a130bdda..88e748b51 100644 --- a/contracts/proposal/dao-proposal-single/src/state.rs +++ b/contracts/proposal/dao-proposal-single/src/state.rs @@ -3,7 +3,9 @@ use cosmwasm_std::{Addr, Uint128}; use cw_hooks::Hooks; use cw_storage_plus::{Item, Map}; use cw_utils::Duration; -use dao_voting::{pre_propose::ProposalCreationPolicy, threshold::Threshold, voting::Vote}; +use dao_voting::{ + pre_propose::ProposalCreationPolicy, threshold::Threshold, veto::VetoConfig, voting::Vote, +}; use crate::proposal::SingleChoiceProposal; @@ -21,6 +23,7 @@ pub struct Ballot { #[serde(default)] pub rationale: Option, } + /// The governance module's configuration. #[cw_serde] pub struct Config { @@ -55,6 +58,9 @@ pub struct Config { /// remain open until the DAO's treasury was large enough for it to be /// executed. pub close_proposal_on_execution_failure: bool, + /// Optional veto configuration. If set to `None`, veto option + /// is disabled. Otherwise contains the configuration for veto flow. + pub veto: Option, } /// The current top level config for the module. The "config" key was diff --git a/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs b/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs index 19d3900e9..c5e419d9b 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs @@ -160,6 +160,7 @@ pub fn test_executed_prop_state_remains_after_vote_swing() { let mut app = App::default(); let instantiate = InstantiateMsg { + veto: None, threshold: AbsolutePercentage { percentage: PercentageThreshold::Percent(Decimal::percent(15)), }, @@ -256,6 +257,7 @@ pub fn test_passed_prop_state_remains_after_vote_swing() { let mut app = App::default(); let instantiate = InstantiateMsg { + veto: None, threshold: AbsolutePercentage { percentage: PercentageThreshold::Percent(Decimal::percent(15)), }, diff --git a/contracts/proposal/dao-proposal-single/src/testing/contracts.rs b/contracts/proposal/dao-proposal-single/src/testing/contracts.rs index 345cf5a3b..d222acbc5 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/contracts.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/contracts.rs @@ -39,17 +39,6 @@ pub(crate) fn cw20_stake_contract() -> Box> { Box::new(contract) } -pub(crate) fn v1_proposal_single_contract() -> Box> { - let contract = ContractWrapper::new( - cw_proposal_single_v1::contract::execute, - cw_proposal_single_v1::contract::instantiate, - cw_proposal_single_v1::contract::query, - ) - .with_reply(cw_proposal_single_v1::contract::reply) - .with_migrate(cw_proposal_single_v1::contract::migrate); - Box::new(contract) -} - pub(crate) fn proposal_single_contract() -> Box> { let contract = ContractWrapper::new( crate::contract::execute, diff --git a/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs b/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs index a97e23c23..aad0cddf8 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs @@ -1,3 +1,5 @@ +use std::mem::discriminant; + use cosmwasm_std::{coins, Addr, Coin, Uint128}; use cw20::Cw20Coin; @@ -132,6 +134,7 @@ where let max_voting_period = cw_utils::Duration::Height(6); let instantiate = InstantiateMsg { + veto: None, threshold, max_voting_period, min_voting_period: None, @@ -280,7 +283,11 @@ where .query_wasm_smart(proposal_single, &QueryMsg::Proposal { proposal_id: 1 }) .unwrap(); - assert_eq!(proposal.proposal.status, expected_status); + // We just care about getting the right variant + assert_eq!( + discriminant::(&proposal.proposal.status), + discriminant::(&expected_status) + ); (app, core_addr) } diff --git a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs index 3fd485f62..14b84d3e2 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs @@ -49,6 +49,7 @@ pub(crate) fn get_pre_propose_info( pub(crate) fn get_default_token_dao_proposal_module_instantiate(app: &mut App) -> InstantiateMsg { InstantiateMsg { + veto: None, threshold: ThresholdQuorum { quorum: PercentageThreshold::Percent(Decimal::percent(15)), threshold: PercentageThreshold::Majority {}, @@ -75,6 +76,7 @@ pub(crate) fn get_default_non_token_dao_proposal_module_instantiate( app: &mut App, ) -> InstantiateMsg { InstantiateMsg { + veto: None, threshold: ThresholdQuorum { threshold: PercentageThreshold::Percent(Decimal::percent(15)), quorum: PercentageThreshold::Majority {}, diff --git a/contracts/proposal/dao-proposal-single/src/testing/migration_tests.rs b/contracts/proposal/dao-proposal-single/src/testing/migration_tests.rs index 3d68ffb7a..3dc5c33e8 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/migration_tests.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/migration_tests.rs @@ -1,13 +1,16 @@ use cosmwasm_std::{to_json_binary, Addr, Uint128, WasmMsg}; use cw20::Cw20Coin; use cw_multi_test::{next_block, App, Executor}; +use cw_utils::Duration; use dao_interface::query::{GetItemResponse, ProposalModuleCountResponse}; use dao_testing::contracts::{ cw20_base_contract, cw20_stake_contract, cw20_staked_balances_voting_contract, dao_dao_contract, proposal_single_contract, v1_dao_dao_contract, v1_proposal_single_contract, }; +use dao_voting::veto::VetoConfig; use dao_voting::{deposit::UncheckedDepositInfo, status::Status}; +use crate::testing::queries::query_list_proposals; use crate::testing::{ execute::{execute_proposal, make_proposal, vote_on_proposal}, instantiate::get_pre_propose_info, @@ -306,6 +309,8 @@ fn test_v1_v2_full_migration() { }), false, ); + + // now migrate with valid config app.execute_contract( sender.clone(), proposal.clone(), @@ -329,6 +334,12 @@ fn test_v1_v2_full_migration() { msg: to_json_binary(&crate::msg::MigrateMsg::FromV1 { close_proposal_on_execution_failure: true, pre_propose_info, + veto: Some(VetoConfig { + timelock_duration: Duration::Height(10), + vetoer: sender.to_string(), + early_execute: true, + veto_before_passed: false, + }), }) .unwrap(), } @@ -370,6 +381,12 @@ fn test_v1_v2_full_migration() { let count = query_proposal_count(&app, &proposal); assert_eq!(count, 3); + let migrated_existing_props = query_list_proposals(&app, &proposal, None, None); + // assert that even though we migrate with a veto config, + // existing proposals are not affected + for prop in migrated_existing_props.proposals { + assert_eq!(prop.proposal.veto, None); + } // ---- // check that proposal module counts have been updated. // ---- @@ -429,6 +446,18 @@ fn test_v1_v2_full_migration() { 4, dao_voting::voting::Vote::Yes, ); + + let new_prop = query_proposal(&app, &proposal, 4); + assert_eq!( + new_prop.proposal.veto, + Some(VetoConfig { + timelock_duration: Duration::Height(10), + vetoer: sender.to_string(), + early_execute: true, + veto_before_passed: false, + }) + ); + execute_proposal(&mut app, &proposal, sender.as_str(), 4); let tokens: Vec = app .wrap() diff --git a/contracts/proposal/dao-proposal-single/src/testing/tests.rs b/contracts/proposal/dao-proposal-single/src/testing/tests.rs index a5cc7aa01..63ddd5b9f 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/tests.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/tests.rs @@ -1,3 +1,5 @@ +use std::ops::Add; + use cosmwasm_std::{ coins, testing::{mock_dependencies, mock_env}, @@ -25,6 +27,7 @@ use dao_voting::{ }, status::Status, threshold::{ActiveThreshold, PercentageThreshold, Threshold}, + veto::{VetoConfig, VetoError}, voting::{Vote, Votes}, }; @@ -35,11 +38,7 @@ use crate::{ query::{ProposalResponse, VoteInfo}, state::Config, testing::{ - contracts::{ - cw20_base_contract, cw20_stake_contract, cw20_staked_balances_voting_contract, - cw_core_contract, pre_propose_single_contract, proposal_single_contract, - v1_proposal_single_contract, - }, + contracts::{pre_propose_single_contract, proposal_single_contract}, execute::{ add_proposal_hook, add_proposal_hook_should_fail, add_vote_hook, add_vote_hook_should_fail, close_proposal, close_proposal_should_fail, @@ -130,6 +129,7 @@ fn test_simple_propose_staked_balances() { total_power: Uint128::new(100_000_000), msgs: vec![], status: Status::Open, + veto: None, votes: Votes::zero(), }; @@ -179,6 +179,7 @@ fn test_simple_proposal_cw4_voting() { total_power: Uint128::new(1), msgs: vec![], status: Status::Open, + veto: None, votes: Votes::zero(), }; @@ -284,6 +285,7 @@ fn test_instantiate_with_non_voting_module_cw20_deposit() { msgs: vec![], status: Status::Open, votes: Votes::zero(), + veto: None, }; assert_eq!(created.proposal, expected); @@ -302,14 +304,1207 @@ fn test_instantiate_with_non_voting_module_cw20_deposit() { refund_policy: dao_voting::deposit::DepositRefundPolicy::OnlyPassed }) ); -} - -#[test] -fn test_proposal_message_execution() { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.close_proposal_on_execution_failure = false; - let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); +} + +#[test] +fn test_proposal_message_execution() { + let mut app = App::default(); + let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); + instantiate.close_proposal_on_execution_failure = false; + let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); + let proposal_module = query_single_proposal_module(&app, &core_addr); + let gov_token = query_dao_token(&app, &core_addr); + + mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); + let proposal_id = make_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + vec![ + WasmMsg::Execute { + contract_addr: gov_token.to_string(), + msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { + recipient: CREATOR_ADDR.to_string(), + amount: Uint128::new(10_000_000), + }) + .unwrap(), + funds: vec![], + } + .into(), + BankMsg::Send { + to_address: CREATOR_ADDR.to_string(), + amount: coins(10, "ujuno"), + } + .into(), + ], + ); + let cw20_balance = query_balance_cw20(&app, &gov_token, CREATOR_ADDR); + let native_balance = query_balance_native(&app, CREATOR_ADDR, "ujuno"); + assert_eq!(cw20_balance, Uint128::zero()); + assert_eq!(native_balance, Uint128::zero()); + + vote_on_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + proposal_id, + Vote::Yes, + ); + let proposal = query_proposal(&app, &proposal_module, proposal_id); + assert_eq!(proposal.proposal.status, Status::Passed); + + // Can't use library function because we expect this to fail due + // to insufficent balance in the bank module. + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + proposal_module.clone(), + &ExecuteMsg::Execute { proposal_id }, + &[], + ) + .unwrap_err(); + let proposal = query_proposal(&app, &proposal_module, proposal_id); + assert_eq!(proposal.proposal.status, Status::Passed); + + mint_natives(&mut app, core_addr.as_str(), coins(10, "ujuno")); + execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); + let proposal = query_proposal(&app, &proposal_module, proposal_id); + assert_eq!(proposal.proposal.status, Status::Executed); + + let cw20_balance = query_balance_cw20(&app, &gov_token, CREATOR_ADDR); + let native_balance = query_balance_native(&app, CREATOR_ADDR, "ujuno"); + assert_eq!(cw20_balance, Uint128::new(20_000_000)); + assert_eq!(native_balance, Uint128::new(10)); + + // Sneak in a check here that proposals can't be executed more + // than once in the on close on execute config suituation. + let err = execute_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); + assert!(matches!(err, ContractError::NotPassed {})) +} + +#[test] +fn test_proposal_message_timelock_execution() -> anyhow::Result<()> { + let mut app = App::default(); + let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); + let veto_config = VetoConfig { + timelock_duration: Duration::Time(100), + vetoer: "oversight".to_string(), + early_execute: false, + veto_before_passed: false, + }; + instantiate.close_proposal_on_execution_failure = false; + instantiate.veto = Some(veto_config.clone()); + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + instantiate, + Some(vec![ + Cw20Coin { + address: "oversight".to_string(), + amount: Uint128::new(15), + }, + Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::new(85), + }, + ]), + ); + let proposal_module = query_single_proposal_module(&app, &core_addr); + let gov_token = query_dao_token(&app, &core_addr); + + mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); + let proposal_id = make_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + vec![ + WasmMsg::Execute { + contract_addr: gov_token.to_string(), + msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { + recipient: CREATOR_ADDR.to_string(), + amount: Uint128::new(10_000_000), + }) + .unwrap(), + funds: vec![], + } + .into(), + BankMsg::Send { + to_address: CREATOR_ADDR.to_string(), + amount: coins(10, "ujuno"), + } + .into(), + ], + ); + let cw20_balance = query_balance_cw20(&app, &gov_token, CREATOR_ADDR); + let native_balance = query_balance_native(&app, CREATOR_ADDR, "ujuno"); + assert_eq!(cw20_balance, Uint128::zero()); + assert_eq!(native_balance, Uint128::zero()); + + vote_on_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + proposal_id, + Vote::Yes, + ); + let proposal = query_proposal(&app, &proposal_module, proposal_id); + + // Proposal is timelocked to the moment of prop expiring + timelock delay + assert_eq!( + proposal.proposal.status, + Status::VetoTimelock { + expiration: proposal + .proposal + .expiration + .add(veto_config.timelock_duration)?, + } + ); + + mint_natives(&mut app, core_addr.as_str(), coins(10, "ujuno")); + + // vetoer can't execute when timelock is active and + // early execute not enabled. + let err: ContractError = app + .execute_contract( + Addr::unchecked("oversight"), + proposal_module.clone(), + &ExecuteMsg::Execute { proposal_id }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, ContractError::VetoError(VetoError::NoEarlyExecute {})); + + // Proposal cannot be excuted before timelock expires + let err: ContractError = app + .execute_contract( + Addr::unchecked(CREATOR_ADDR), + proposal_module.clone(), + &ExecuteMsg::Execute { proposal_id }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!(err, ContractError::VetoError(VetoError::Timelocked {})); + + // Time passes + app.update_block(|block| { + block.time = block.time.plus_seconds(604800 + 200); + }); + + // Proposal executes successfully + execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); + let proposal = query_proposal(&app, &proposal_module, proposal_id); + assert_eq!(proposal.proposal.status, Status::Executed); + + Ok(()) +} + +// only the authorized vetoer can veto an open proposal +#[test] +fn test_open_proposal_veto_unauthorized() { + let mut app = App::default(); + let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); + instantiate.close_proposal_on_execution_failure = false; + let veto_config = VetoConfig { + timelock_duration: Duration::Time(100), + vetoer: "oversight".to_string(), + early_execute: false, + veto_before_passed: true, + }; + instantiate.veto = Some(veto_config.clone()); + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + instantiate, + Some(vec![Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::new(85), + }]), + ); + let proposal_module = query_single_proposal_module(&app, &core_addr); + let gov_token = query_dao_token(&app, &core_addr); + + mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); + let proposal_id = make_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + vec![ + WasmMsg::Execute { + contract_addr: gov_token.to_string(), + msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { + recipient: CREATOR_ADDR.to_string(), + amount: Uint128::new(10_000_000), + }) + .unwrap(), + funds: vec![], + } + .into(), + BankMsg::Send { + to_address: CREATOR_ADDR.to_string(), + amount: coins(10, "ujuno"), + } + .into(), + ], + ); + + // only the vetoer can veto + let err: ContractError = app + .execute_contract( + Addr::unchecked("not-oversight"), + proposal_module.clone(), + &ExecuteMsg::Veto { proposal_id }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, ContractError::VetoError(VetoError::Unauthorized {})); +} + +// open proposal can only be vetoed if `veto_before_passed` flag is enabled +#[test] +fn test_open_proposal_veto_with_early_veto_flag_disabled() { + let mut app = App::default(); + let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); + instantiate.close_proposal_on_execution_failure = false; + let veto_config = VetoConfig { + timelock_duration: Duration::Time(100), + vetoer: "oversight".to_string(), + early_execute: false, + veto_before_passed: false, + }; + instantiate.veto = Some(veto_config.clone()); + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + instantiate, + Some(vec![Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::new(85), + }]), + ); + let proposal_module = query_single_proposal_module(&app, &core_addr); + let gov_token = query_dao_token(&app, &core_addr); + + mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); + let proposal_id = make_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + vec![ + WasmMsg::Execute { + contract_addr: gov_token.to_string(), + msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { + recipient: CREATOR_ADDR.to_string(), + amount: Uint128::new(10_000_000), + }) + .unwrap(), + funds: vec![], + } + .into(), + BankMsg::Send { + to_address: CREATOR_ADDR.to_string(), + amount: coins(10, "ujuno"), + } + .into(), + ], + ); + + let err: ContractError = app + .execute_contract( + Addr::unchecked("oversight"), + proposal_module.clone(), + &ExecuteMsg::Veto { proposal_id }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!( + err, + ContractError::VetoError(VetoError::NoVetoBeforePassed {}) + ); +} + +#[test] +fn test_open_proposal_veto_with_no_timelock() { + let mut app = App::default(); + let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); + instantiate.close_proposal_on_execution_failure = false; + instantiate.veto = None; + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + instantiate, + Some(vec![Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::new(85), + }]), + ); + let proposal_module = query_single_proposal_module(&app, &core_addr); + let gov_token = query_dao_token(&app, &core_addr); + + mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); + let proposal_id = make_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + vec![ + WasmMsg::Execute { + contract_addr: gov_token.to_string(), + msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { + recipient: CREATOR_ADDR.to_string(), + amount: Uint128::new(10_000_000), + }) + .unwrap(), + funds: vec![], + } + .into(), + BankMsg::Send { + to_address: CREATOR_ADDR.to_string(), + amount: coins(10, "ujuno"), + } + .into(), + ], + ); + + let err: ContractError = app + .execute_contract( + Addr::unchecked("oversight"), + proposal_module.clone(), + &ExecuteMsg::Veto { proposal_id }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!( + err, + ContractError::VetoError(VetoError::NoVetoConfiguration {}) + ); +} + +// if proposal is not open or timelocked, attempts to veto should +// throw an error +#[test] +fn test_vetoed_proposal_veto() { + let mut app = App::default(); + let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); + instantiate.close_proposal_on_execution_failure = false; + let veto_config = VetoConfig { + timelock_duration: Duration::Time(100), + vetoer: "oversight".to_string(), + early_execute: false, + veto_before_passed: true, + }; + instantiate.veto = Some(veto_config.clone()); + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + instantiate, + Some(vec![Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::new(85), + }]), + ); + let proposal_module = query_single_proposal_module(&app, &core_addr); + let gov_token = query_dao_token(&app, &core_addr); + + mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); + let proposal_id = make_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + vec![ + WasmMsg::Execute { + contract_addr: gov_token.to_string(), + msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { + recipient: CREATOR_ADDR.to_string(), + amount: Uint128::new(10_000_000), + }) + .unwrap(), + funds: vec![], + } + .into(), + BankMsg::Send { + to_address: CREATOR_ADDR.to_string(), + amount: coins(10, "ujuno"), + } + .into(), + ], + ); + + app.execute_contract( + Addr::unchecked("oversight"), + proposal_module.clone(), + &ExecuteMsg::Veto { proposal_id }, + &[], + ) + .unwrap(); + + let proposal = query_proposal(&app, &proposal_module, proposal_id); + assert_eq!(proposal.proposal.status, Status::Vetoed {}); + + let err: ContractError = app + .execute_contract( + Addr::unchecked("oversight"), + proposal_module.clone(), + &ExecuteMsg::Veto { proposal_id }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!( + ContractError::VetoError(VetoError::InvalidProposalStatus { + status: "vetoed".to_string() + }), + err, + ); +} + +#[test] +fn test_open_proposal_veto_early() { + let mut app = App::default(); + let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); + instantiate.close_proposal_on_execution_failure = false; + let veto_config = VetoConfig { + timelock_duration: Duration::Time(100), + vetoer: "oversight".to_string(), + early_execute: false, + veto_before_passed: true, + }; + instantiate.veto = Some(veto_config.clone()); + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + instantiate, + Some(vec![Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::new(85), + }]), + ); + let proposal_module = query_single_proposal_module(&app, &core_addr); + let gov_token = query_dao_token(&app, &core_addr); + + mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); + let proposal_id = make_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + vec![ + WasmMsg::Execute { + contract_addr: gov_token.to_string(), + msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { + recipient: CREATOR_ADDR.to_string(), + amount: Uint128::new(10_000_000), + }) + .unwrap(), + funds: vec![], + } + .into(), + BankMsg::Send { + to_address: CREATOR_ADDR.to_string(), + amount: coins(10, "ujuno"), + } + .into(), + ], + ); + + app.execute_contract( + Addr::unchecked("oversight"), + proposal_module.clone(), + &ExecuteMsg::Veto { proposal_id }, + &[], + ) + .unwrap(); + + let proposal = query_proposal(&app, &proposal_module, proposal_id); + assert_eq!(proposal.proposal.status, Status::Vetoed {}); +} + +// only the vetoer can veto during timelock period +#[test] +fn test_timelocked_proposal_veto_unauthorized() -> anyhow::Result<()> { + let mut app = App::default(); + let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); + instantiate.close_proposal_on_execution_failure = false; + let veto_config = VetoConfig { + timelock_duration: Duration::Time(100), + vetoer: "oversight".to_string(), + early_execute: true, + veto_before_passed: false, + }; + instantiate.veto = Some(veto_config.clone()); + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + instantiate, + Some(vec![ + Cw20Coin { + address: "oversight".to_string(), + amount: Uint128::new(15), + }, + Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::new(85), + }, + ]), + ); + let proposal_module = query_single_proposal_module(&app, &core_addr); + let gov_token = query_dao_token(&app, &core_addr); + + mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); + let proposal_id = make_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + vec![ + WasmMsg::Execute { + contract_addr: gov_token.to_string(), + msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { + recipient: CREATOR_ADDR.to_string(), + amount: Uint128::new(10_000_000), + }) + .unwrap(), + funds: vec![], + } + .into(), + BankMsg::Send { + to_address: CREATOR_ADDR.to_string(), + amount: coins(10, "ujuno"), + } + .into(), + ], + ); + + vote_on_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + proposal_id, + Vote::Yes, + ); + let proposal = query_proposal(&app, &proposal_module, proposal_id); + + // Proposal is timelocked to the moment of prop expiring + timelock delay + assert_eq!( + proposal.proposal.status, + Status::VetoTimelock { + expiration: proposal + .proposal + .expiration + .add(veto_config.timelock_duration)?, + } + ); + + let err: ContractError = app + .execute_contract( + Addr::unchecked("not-oversight"), + proposal_module.clone(), + &ExecuteMsg::Veto { proposal_id }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!(err, ContractError::VetoError(VetoError::Unauthorized {}),); + let proposal = query_proposal(&app, &proposal_module, proposal_id); + assert_eq!( + proposal.proposal.status, + Status::VetoTimelock { + expiration: proposal + .proposal + .expiration + .add(veto_config.timelock_duration)?, + } + ); + + Ok(()) +} + +// vetoer can only veto the proposal before the timelock expires +#[test] +fn test_timelocked_proposal_veto_expired_timelock() -> anyhow::Result<()> { + let mut app = App::default(); + let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); + instantiate.close_proposal_on_execution_failure = false; + let veto_config = VetoConfig { + timelock_duration: Duration::Time(100), + vetoer: "oversight".to_string(), + early_execute: true, + veto_before_passed: false, + }; + instantiate.veto = Some(veto_config.clone()); + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + instantiate, + Some(vec![ + Cw20Coin { + address: "oversight".to_string(), + amount: Uint128::new(15), + }, + Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::new(85), + }, + ]), + ); + let proposal_module = query_single_proposal_module(&app, &core_addr); + let gov_token = query_dao_token(&app, &core_addr); + + mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); + let proposal_id = make_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + vec![ + WasmMsg::Execute { + contract_addr: gov_token.to_string(), + msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { + recipient: CREATOR_ADDR.to_string(), + amount: Uint128::new(10_000_000), + }) + .unwrap(), + funds: vec![], + } + .into(), + BankMsg::Send { + to_address: CREATOR_ADDR.to_string(), + amount: coins(10, "ujuno"), + } + .into(), + ], + ); + + vote_on_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + proposal_id, + Vote::Yes, + ); + let proposal = query_proposal(&app, &proposal_module, proposal_id); + + // Proposal is timelocked to the moment of prop expiring + timelock delay + assert_eq!( + proposal.proposal.status, + Status::VetoTimelock { + expiration: proposal + .proposal + .expiration + .add(veto_config.timelock_duration)?, + } + ); + app.update_block(|b| b.time = b.time.plus_seconds(604800 + 200)); + + let err: ContractError = app + .execute_contract( + Addr::unchecked("oversight"), + proposal_module.clone(), + &ExecuteMsg::Veto { proposal_id }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!(err, ContractError::VetoError(VetoError::TimelockExpired {}),); + + Ok(()) +} + +// vetoer can only exec timelocked prop if the early exec flag is enabled +#[test] +fn test_timelocked_proposal_execute_no_early_exec() -> anyhow::Result<()> { + let mut app = App::default(); + let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); + instantiate.close_proposal_on_execution_failure = false; + let veto_config = VetoConfig { + timelock_duration: Duration::Time(100), + vetoer: "oversight".to_string(), + early_execute: false, + veto_before_passed: false, + }; + instantiate.veto = Some(veto_config.clone()); + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + instantiate, + Some(vec![Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::new(85), + }]), + ); + let proposal_module = query_single_proposal_module(&app, &core_addr); + let gov_token = query_dao_token(&app, &core_addr); + + mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); + let proposal_id = make_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + vec![ + WasmMsg::Execute { + contract_addr: gov_token.to_string(), + msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { + recipient: CREATOR_ADDR.to_string(), + amount: Uint128::new(10_000_000), + }) + .unwrap(), + funds: vec![], + } + .into(), + BankMsg::Send { + to_address: CREATOR_ADDR.to_string(), + amount: coins(10, "ujuno"), + } + .into(), + ], + ); + + vote_on_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + proposal_id, + Vote::Yes, + ); + let proposal = query_proposal(&app, &proposal_module, proposal_id); + + // Proposal is timelocked to the moment of prop expiring + timelock delay + assert_eq!( + proposal.proposal.status, + Status::VetoTimelock { + expiration: proposal + .proposal + .expiration + .add(veto_config.timelock_duration)?, + } + ); + + let err: ContractError = app + .execute_contract( + Addr::unchecked("oversight"), + proposal_module.clone(), + &ExecuteMsg::Execute { proposal_id }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!(err, ContractError::VetoError(VetoError::NoEarlyExecute {}),); + + Ok(()) +} + +#[test] +fn test_timelocked_proposal_execute_early() -> anyhow::Result<()> { + let mut app = App::default(); + let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); + instantiate.close_proposal_on_execution_failure = false; + let veto_config = VetoConfig { + timelock_duration: Duration::Time(100), + vetoer: "oversight".to_string(), + early_execute: true, + veto_before_passed: false, + }; + instantiate.veto = Some(veto_config.clone()); + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + instantiate, + Some(vec![Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::new(85), + }]), + ); + let proposal_module = query_single_proposal_module(&app, &core_addr); + let gov_token = query_dao_token(&app, &core_addr); + + mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); + let proposal_id = make_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + vec![ + WasmMsg::Execute { + contract_addr: gov_token.to_string(), + msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { + recipient: CREATOR_ADDR.to_string(), + amount: Uint128::new(10_000_000), + }) + .unwrap(), + funds: vec![], + } + .into(), + BankMsg::Send { + to_address: CREATOR_ADDR.to_string(), + amount: coins(10, "ujuno"), + } + .into(), + ], + ); + + vote_on_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + proposal_id, + Vote::Yes, + ); + let proposal = query_proposal(&app, &proposal_module, proposal_id); + + // Proposal is timelocked to the moment of prop expiring + timelock delay + assert_eq!( + proposal.proposal.status, + Status::VetoTimelock { + expiration: proposal + .proposal + .expiration + .add(veto_config.timelock_duration)?, + } + ); + + // assert timelock is active + assert!(!veto_config + .timelock_duration + .after(&app.block_info()) + .is_expired(&app.block_info())); + mint_natives(&mut app, core_addr.as_str(), coins(10, "ujuno")); + + app.execute_contract( + Addr::unchecked("oversight"), + proposal_module.clone(), + &ExecuteMsg::Execute { proposal_id }, + &[], + ) + .unwrap(); + + let proposal = query_proposal(&app, &proposal_module, proposal_id); + assert_eq!(proposal.proposal.status, Status::Executed {}); + + Ok(()) +} + +// only vetoer can exec timelocked prop early +#[test] +fn test_timelocked_proposal_execute_active_timelock_unauthorized() -> anyhow::Result<()> { + let mut app = App::default(); + let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); + instantiate.close_proposal_on_execution_failure = false; + let veto_config = VetoConfig { + timelock_duration: Duration::Time(100), + vetoer: "oversight".to_string(), + early_execute: true, + veto_before_passed: false, + }; + instantiate.veto = Some(veto_config.clone()); + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + instantiate, + Some(vec![Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::new(85), + }]), + ); + let proposal_module = query_single_proposal_module(&app, &core_addr); + let gov_token = query_dao_token(&app, &core_addr); + + mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); + let proposal_id = make_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + vec![ + WasmMsg::Execute { + contract_addr: gov_token.to_string(), + msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { + recipient: CREATOR_ADDR.to_string(), + amount: Uint128::new(10_000_000), + }) + .unwrap(), + funds: vec![], + } + .into(), + BankMsg::Send { + to_address: CREATOR_ADDR.to_string(), + amount: coins(10, "ujuno"), + } + .into(), + ], + ); + + vote_on_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + proposal_id, + Vote::Yes, + ); + let proposal = query_proposal(&app, &proposal_module, proposal_id); + + // Proposal is timelocked to the moment of prop expiring + timelock delay + assert_eq!( + proposal.proposal.status, + Status::VetoTimelock { + expiration: proposal + .proposal + .expiration + .add(veto_config.timelock_duration)?, + } + ); + + // assert timelock is active + assert!(!veto_config + .timelock_duration + .after(&app.block_info()) + .is_expired(&app.block_info())); + + let err: ContractError = app + .execute_contract( + Addr::unchecked(CREATOR_ADDR), + proposal_module.clone(), + &ExecuteMsg::Execute { proposal_id }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!(err, ContractError::VetoError(VetoError::Timelocked {}),); + + Ok(()) +} + +// anyone can exec the prop after the timelock expires +#[test] +fn test_timelocked_proposal_execute_expired_timelock_not_vetoer() -> anyhow::Result<()> { + let mut app = App::default(); + let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); + instantiate.close_proposal_on_execution_failure = false; + let veto_config = VetoConfig { + timelock_duration: Duration::Time(100), + vetoer: "oversight".to_string(), + early_execute: true, + veto_before_passed: false, + }; + instantiate.veto = Some(veto_config.clone()); + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + instantiate, + Some(vec![Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::new(85), + }]), + ); + let proposal_module = query_single_proposal_module(&app, &core_addr); + let gov_token = query_dao_token(&app, &core_addr); + + mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); + let proposal_id = make_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + vec![ + WasmMsg::Execute { + contract_addr: gov_token.to_string(), + msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { + recipient: CREATOR_ADDR.to_string(), + amount: Uint128::new(10_000_000), + }) + .unwrap(), + funds: vec![], + } + .into(), + BankMsg::Send { + to_address: CREATOR_ADDR.to_string(), + amount: coins(10, "ujuno"), + } + .into(), + ], + ); + + vote_on_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + proposal_id, + Vote::Yes, + ); + let proposal = query_proposal(&app, &proposal_module, proposal_id); + + // Proposal is timelocked to the moment of prop expiring + timelock delay + let expiration = proposal + .proposal + .expiration + .add(veto_config.timelock_duration)?; + assert_eq!( + proposal.proposal.status, + Status::VetoTimelock { expiration } + ); + + app.update_block(|b| b.time = b.time.plus_seconds(604800 + 201)); + // assert timelock is expired + assert!(expiration.is_expired(&app.block_info())); + mint_natives(&mut app, core_addr.as_str(), coins(10, "ujuno")); + + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + proposal_module.clone(), + &ExecuteMsg::Execute { proposal_id }, + &[], + ) + .unwrap(); + + let proposal = query_proposal(&app, &proposal_module, proposal_id); + assert_eq!(proposal.proposal.status, Status::Executed {},); + + Ok(()) +} + +#[test] +fn test_proposal_message_timelock_veto() -> anyhow::Result<()> { + let mut app = App::default(); + let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); + instantiate.close_proposal_on_execution_failure = false; + let veto_config = VetoConfig { + timelock_duration: Duration::Time(100), + vetoer: "oversight".to_string(), + early_execute: false, + veto_before_passed: false, + }; + instantiate.veto = Some(veto_config.clone()); + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + instantiate, + Some(vec![Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::new(85), + }]), + ); + let proposal_module = query_single_proposal_module(&app, &core_addr); + let gov_token = query_dao_token(&app, &core_addr); + + mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); + let proposal_id = make_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + vec![ + WasmMsg::Execute { + contract_addr: gov_token.to_string(), + msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { + recipient: CREATOR_ADDR.to_string(), + amount: Uint128::new(10_000_000), + }) + .unwrap(), + funds: vec![], + } + .into(), + BankMsg::Send { + to_address: CREATOR_ADDR.to_string(), + amount: coins(10, "ujuno"), + } + .into(), + ], + ); + let cw20_balance = query_balance_cw20(&app, &gov_token, CREATOR_ADDR); + let native_balance = query_balance_native(&app, CREATOR_ADDR, "ujuno"); + assert_eq!(cw20_balance, Uint128::zero()); + assert_eq!(native_balance, Uint128::zero()); + + // Vetoer can't veto early + let err: ContractError = app + .execute_contract( + Addr::unchecked("oversight"), + proposal_module.clone(), + &ExecuteMsg::Veto { proposal_id }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!( + err, + ContractError::VetoError(VetoError::NoVetoBeforePassed {}) + ); + + // Vote on proposal to pass it + vote_on_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + proposal_id, + Vote::Yes, + ); + let proposal = query_proposal(&app, &proposal_module, proposal_id); + + // Proposal is timelocked to the moment of prop expiring + timelock delay + assert_eq!( + proposal.proposal.status, + Status::VetoTimelock { + expiration: proposal + .proposal + .expiration + .add(veto_config.timelock_duration)?, + } + ); + + mint_natives(&mut app, core_addr.as_str(), coins(10, "ujuno")); + + // Non-vetoer cannot veto + let err: ContractError = app + .execute_contract( + Addr::unchecked(CREATOR_ADDR), + proposal_module.clone(), + &ExecuteMsg::Veto { proposal_id }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, ContractError::VetoError(VetoError::Unauthorized {})); + + // Oversite vetos prop + app.execute_contract( + Addr::unchecked("oversight"), + proposal_module.clone(), + &ExecuteMsg::Veto { proposal_id }, + &[], + ) + .unwrap(); + + let proposal = query_proposal(&app, &proposal_module, proposal_id); + assert_eq!(proposal.proposal.status, Status::Vetoed); + + Ok(()) +} + +#[test] +fn test_proposal_message_timelock_early_execution() -> anyhow::Result<()> { + let mut app = App::default(); + let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); + instantiate.close_proposal_on_execution_failure = false; + let veto_config = VetoConfig { + timelock_duration: Duration::Time(100), + vetoer: "oversight".to_string(), + early_execute: true, + veto_before_passed: false, + }; + instantiate.veto = Some(veto_config.clone()); + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + instantiate, + Some(vec![ + Cw20Coin { + address: "oversight".to_string(), + amount: Uint128::new(15), + }, + Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::new(85), + }, + ]), + ); let proposal_module = query_single_proposal_module(&app, &core_addr); let gov_token = query_dao_token(&app, &core_addr); @@ -349,34 +1544,103 @@ fn test_proposal_message_execution() { Vote::Yes, ); let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Passed); - // Can't use library function because we expect this to fail due - // to insufficent balance in the bank module. + // Proposal is timelocked to the moment of prop expiring + timelock delay + assert_eq!( + proposal.proposal.status, + Status::VetoTimelock { + expiration: proposal + .proposal + .expiration + .add(veto_config.timelock_duration)?, + } + ); + + mint_natives(&mut app, core_addr.as_str(), coins(10, "ujuno")); + + // Proposal can be executed early by vetoer + execute_proposal(&mut app, &proposal_module, "oversight", proposal_id); + let proposal = query_proposal(&app, &proposal_module, proposal_id); + assert_eq!(proposal.proposal.status, Status::Executed); + + Ok(()) +} + +#[test] +fn test_proposal_message_timelock_veto_before_passed() { + let mut app = App::default(); + let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); + instantiate.close_proposal_on_execution_failure = false; + instantiate.veto = Some(VetoConfig { + timelock_duration: Duration::Time(100), + vetoer: "oversight".to_string(), + early_execute: false, + veto_before_passed: true, + }); + let core_addr = instantiate_with_staked_balances_governance( + &mut app, + instantiate, + Some(vec![ + Cw20Coin { + address: "oversight".to_string(), + amount: Uint128::new(15), + }, + Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::new(85), + }, + ]), + ); + let proposal_module = query_single_proposal_module(&app, &core_addr); + let gov_token = query_dao_token(&app, &core_addr); + + mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); + let proposal_id = make_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + vec![ + WasmMsg::Execute { + contract_addr: gov_token.to_string(), + msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { + recipient: CREATOR_ADDR.to_string(), + amount: Uint128::new(10_000_000), + }) + .unwrap(), + funds: vec![], + } + .into(), + BankMsg::Send { + to_address: CREATOR_ADDR.to_string(), + amount: coins(10, "ujuno"), + } + .into(), + ], + ); + + let proposal = query_proposal(&app, &proposal_module, proposal_id); + + // Proposal is open for voting + assert_eq!(proposal.proposal.status, Status::Open); + + // Oversite vetos prop app.execute_contract( - Addr::unchecked(CREATOR_ADDR), + Addr::unchecked("oversight"), proposal_module.clone(), - &ExecuteMsg::Execute { proposal_id }, + &ExecuteMsg::Veto { proposal_id }, &[], ) - .unwrap_err(); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Passed); + .unwrap(); - mint_natives(&mut app, core_addr.as_str(), coins(10, "ujuno")); - execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Executed); + assert_eq!(proposal.proposal.status, Status::Vetoed); - let cw20_balance = query_balance_cw20(&app, &gov_token, CREATOR_ADDR); - let native_balance = query_balance_native(&app, CREATOR_ADDR, "ujuno"); - assert_eq!(cw20_balance, Uint128::new(20_000_000)); - assert_eq!(native_balance, Uint128::new(10)); + // mint_natives(&mut app, core_addr.as_str(), coins(10, "ujuno")); - // Sneak in a check here that proposals can't be executed more - // than once in the on close on execute config suituation. - let err = execute_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - assert!(matches!(err, ContractError::NotPassed {})) + // // Proposal can be executed early by vetoer + // execute_proposal(&mut app, &proposal_module, "oversight", proposal_id); + // let proposal = query_proposal(&app, &proposal_module, proposal_id); + // assert_eq!(proposal.proposal.status, Status::Executed); } #[test] @@ -446,7 +1710,7 @@ fn test_proposal_cant_close_after_expiry_is_passed() { // Expire the proposal. This should pass it. app.update_block(|b| b.time = b.time.plus_seconds(604800)); let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Passed); + assert_eq!(proposal.proposal.status, Status::Passed,); // Make sure it can't be closed. let err = close_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); @@ -579,6 +1843,12 @@ fn test_update_config() { vec![WasmMsg::Execute { contract_addr: proposal_module.to_string(), msg: to_json_binary(&ExecuteMsg::UpdateConfig { + veto: Some(VetoConfig { + timelock_duration: Duration::Height(2), + vetoer: CREATOR_ADDR.to_string(), + early_execute: false, + veto_before_passed: false, + }), threshold: Threshold::AbsoluteCount { threshold: Uint128::new(10_000), }, @@ -607,6 +1877,12 @@ fn test_update_config() { assert_eq!( config, Config { + veto: Some(VetoConfig { + timelock_duration: Duration::Height(2), + vetoer: CREATOR_ADDR.to_string(), + early_execute: false, + veto_before_passed: false, + }), threshold: Threshold::AbsoluteCount { threshold: Uint128::new(10_000) }, @@ -623,8 +1899,38 @@ fn test_update_config() { let err: ContractError = app .execute_contract( Addr::unchecked(CREATOR_ADDR), + proposal_module.clone(), + &&ExecuteMsg::UpdateConfig { + veto: None, + threshold: Threshold::AbsoluteCount { + threshold: Uint128::new(10_000), + }, + max_voting_period: Duration::Height(6), + min_voting_period: None, + only_members_execute: true, + allow_revoting: false, + dao: core_addr.to_string(), + close_proposal_on_execution_failure: false, + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert!(matches!(err, ContractError::Unauthorized {})); + + // Check that veto config is validated (mismatching duration units). + let err: ContractError = app + .execute_contract( + Addr::unchecked(core_addr.clone()), proposal_module, &&ExecuteMsg::UpdateConfig { + veto: Some(VetoConfig { + timelock_duration: Duration::Time(100), + vetoer: CREATOR_ADDR.to_string(), + early_execute: false, + veto_before_passed: false, + }), threshold: Threshold::AbsoluteCount { threshold: Uint128::new(10_000), }, @@ -640,7 +1946,10 @@ fn test_update_config() { .unwrap_err() .downcast() .unwrap(); - assert!(matches!(err, ContractError::Unauthorized {})) + assert!(matches!( + err, + ContractError::VetoError(VetoError::TimelockDurationUnitMismatch {}) + )) } #[test] @@ -719,6 +2028,7 @@ fn test_anyone_may_propose_and_proposal_listing() { no: Uint128::zero(), abstain: Uint128::zero() }, + veto: None } } ) @@ -1185,6 +2495,7 @@ fn test_allow_revoting_config_changes() { core_addr.clone(), proposal_module.clone(), &ExecuteMsg::UpdateConfig { + veto: None, threshold: Threshold::ThresholdQuorum { quorum: PercentageThreshold::Percent(Decimal::percent(15)), threshold: PercentageThreshold::Majority {}, @@ -1488,6 +2799,7 @@ fn test_proposal_count_initialized_to_zero() { let core_addr = instantiate_with_staked_balances_governance( &mut app, InstantiateMsg { + veto: None, threshold: Threshold::ThresholdQuorum { threshold: PercentageThreshold::Majority {}, quorum: PercentageThreshold::Percent(Decimal::percent(10)), @@ -1564,295 +2876,277 @@ pub fn test_migrate_updates_version() { assert_eq!(version.contract, CONTRACT_NAME); } -/// Instantiates a DAO with a v1 proposal module and then migrates it -/// to v2. -#[test] -fn test_migrate_from_v1() { - use cw_proposal_single_v1 as v1; - use dao_pre_propose_single as cppbps; - - let mut app = App::default(); - let v1_proposal_single_code = app.store_code(v1_proposal_single_contract()); - - let instantiate = v1::msg::InstantiateMsg { - threshold: voting_v1::Threshold::AbsolutePercentage { - percentage: voting_v1::PercentageThreshold::Majority {}, - }, - max_voting_period: cw_utils_v1::Duration::Height(6), - min_voting_period: None, - only_members_execute: false, - allow_revoting: false, - deposit_info: Some(v1::msg::DepositInfo { - token: v1::msg::DepositToken::VotingModuleToken {}, - deposit: Uint128::new(1), - refund_failed_proposals: true, - }), - }; - - let initial_balances = vec![Cw20Coin { - amount: Uint128::new(100), - address: CREATOR_ADDR.to_string(), - }]; - - let cw20_id = app.store_code(cw20_base_contract()); - let cw20_stake_id = app.store_code(cw20_stake_contract()); - let staked_balances_voting_id = app.store_code(cw20_staked_balances_voting_contract()); - let core_contract_id = app.store_code(cw_core_contract()); - - let instantiate_core = dao_interface::msg::InstantiateMsg { - admin: None, - name: "DAO DAO".to_string(), - description: "A DAO that builds DAOs".to_string(), - image_url: None, - dao_uri: None, - automatically_add_cw20s: true, - automatically_add_cw721s: false, - voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: staked_balances_voting_id, - msg: to_json_binary(&dao_voting_cw20_staked::msg::InstantiateMsg { - active_threshold: None, - token_info: dao_voting_cw20_staked::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: cw20_stake_id, - unstaking_duration: Some(Duration::Height(6)), - initial_dao_balance: None, - }, - }) - .unwrap(), - admin: None, - funds: vec![], - label: "DAO DAO voting module".to_string(), - }, - proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: v1_proposal_single_code, - msg: to_json_binary(&instantiate).unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO governance module.".to_string(), - }], - initial_items: None, - }; - - let core_addr = app - .instantiate_contract( - core_contract_id, - Addr::unchecked(CREATOR_ADDR), - &instantiate_core, - &[], - "DAO DAO", - None, - ) - .unwrap(); - - let core_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart( - core_addr.clone(), - &dao_interface::msg::QueryMsg::DumpState {}, - ) - .unwrap(); - let voting_module = core_state.voting_module; - - let staking_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module.clone(), - &dao_voting_cw20_staked::msg::QueryMsg::StakingContract {}, - ) - .unwrap(); - let token_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module, - &dao_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_json_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), - }, - &[], - ) - .unwrap(); - } - - // Update the block so that those staked balances appear. - app.update_block(|block| block.height += 1); - - let proposal_module = query_single_proposal_module(&app, &core_addr); - - // Make a proposal so we can test that migration doesn't work with - // open proposals that have deposits. - mint_cw20s(&mut app, &token_contract, &core_addr, CREATOR_ADDR, 1); - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - token_contract.clone(), - &cw20::Cw20ExecuteMsg::IncreaseAllowance { - spender: proposal_module.to_string(), - amount: Uint128::new(1), - expires: None, - }, - &[], - ) - .unwrap(); - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &v1::msg::ExecuteMsg::Propose { - title: "title".to_string(), - description: "description".to_string(), - msgs: vec![], - }, - &[], - ) - .unwrap(); - - let v2_proposal_single = app.store_code(proposal_single_contract()); - let pre_propose_single = app.store_code(pre_propose_single_contract()); - - // Attempt to migrate. This will fail as there is a pending - // proposal. - let migrate_msg = MigrateMsg::FromV1 { - close_proposal_on_execution_failure: true, - pre_propose_info: PreProposeInfo::ModuleMayPropose { - info: ModuleInstantiateInfo { - code_id: pre_propose_single, - msg: to_json_binary(&dao_pre_propose_single::InstantiateMsg { - deposit_info: Some(UncheckedDepositInfo { - denom: dao_voting::deposit::DepositToken::VotingModuleToken {}, - amount: Uint128::new(1), - refund_policy: dao_voting::deposit::DepositRefundPolicy::OnlyPassed, - }), - open_proposal_submission: false, - extension: Empty::default(), - }) - .unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO pre-propose".to_string(), - }, - }, - }; - let err: ContractError = app - .execute( - core_addr.clone(), - CosmosMsg::Wasm(WasmMsg::Migrate { - contract_addr: proposal_module.to_string(), - new_code_id: v2_proposal_single, - msg: to_json_binary(&migrate_msg).unwrap(), - }), - ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!(err, ContractError::PendingProposals {})); - - // Vote on and close the pending proposal. - vote_on_proposal(&mut app, &proposal_module, CREATOR_ADDR, 1, Vote::No); - close_proposal(&mut app, &proposal_module, CREATOR_ADDR, 1); - - // Now we can migrate! - app.execute( - core_addr.clone(), - CosmosMsg::Wasm(WasmMsg::Migrate { - contract_addr: proposal_module.to_string(), - new_code_id: v2_proposal_single, - msg: to_json_binary(&migrate_msg).unwrap(), - }), - ) - .unwrap(); - - let new_config = query_proposal_config(&app, &proposal_module); - assert_eq!( - new_config, - Config { - threshold: Threshold::AbsolutePercentage { - percentage: PercentageThreshold::Majority {} - }, - max_voting_period: Duration::Height(6), - min_voting_period: None, - only_members_execute: false, - allow_revoting: false, - dao: core_addr.clone(), - close_proposal_on_execution_failure: true, - } - ); - - // We can not migrate more than once. - let err: ContractError = app - .execute( - core_addr.clone(), - CosmosMsg::Wasm(WasmMsg::Migrate { - contract_addr: proposal_module.to_string(), - new_code_id: v2_proposal_single, - msg: to_json_binary(&migrate_msg).unwrap(), - }), - ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!(err, ContractError::AlreadyMigrated {})); - - // Make sure we can still query for ballots (rationale works post - // migration). - let vote = query_vote(&app, &proposal_module, CREATOR_ADDR, 1); - assert_eq!( - vote.vote.unwrap(), - VoteInfo { - voter: Addr::unchecked(CREATOR_ADDR), - vote: Vote::No, - power: Uint128::new(100), - rationale: None - } - ); - - let proposal_creation_policy = query_creation_policy(&app, &proposal_module); - - // Check that a new creation policy has been birthed. - let pre_propose = match proposal_creation_policy { - ProposalCreationPolicy::Anyone {} => panic!("expected a pre-propose module"), - ProposalCreationPolicy::Module { addr } => addr, - }; - let pre_propose_config = query_pre_proposal_single_config(&app, &pre_propose); - assert_eq!( - pre_propose_config, - cppbps::Config { - open_proposal_submission: false, - deposit_info: Some(CheckedDepositInfo { - denom: CheckedDenom::Cw20(token_contract.clone()), - amount: Uint128::new(1), - refund_policy: dao_voting::deposit::DepositRefundPolicy::OnlyPassed, - }) - } - ); - - // Make sure we can still make a proposal and vote on it. - mint_cw20s(&mut app, &token_contract, &core_addr, CREATOR_ADDR, 1); - let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Executed); -} +// //// TODO test migrate +// /// Instantiates a DAO with a v1 proposal module and then migrates it +// /// to v2. +// #[test] +// fn test_migrate_from_v1() { +// use cw_proposal_single_v1 as v1; +// use dao_pre_propose_single as cppbps; + +// let mut app = App::default(); +// let v1_proposal_single_code = app.store_code(v1_proposal_single_contract()); + +// let instantiate = v1::msg::InstantiateMsg { +// threshold: voting_v1::Threshold::AbsolutePercentage { +// percentage: voting_v1::PercentageThreshold::Majority {}, +// }, +// max_voting_period: cw_utils_v1::Duration::Height(6), +// min_voting_period: None, +// only_members_execute: false, +// allow_revoting: false, +// deposit_info: Some(v1::msg::DepositInfo { +// token: v1::msg::DepositToken::VotingModuleToken {}, +// deposit: Uint128::new(1), +// refund_failed_proposals: true, +// }), +// }; + +// let initial_balances = vec![Cw20Coin { +// amount: Uint128::new(100), +// address: CREATOR_ADDR.to_string(), +// }]; + +// let cw20_id = app.store_code(cw20_base_contract()); +// let cw20_stake_id = app.store_code(cw20_stake_contract()); +// let staked_balances_voting_id = app.store_code(cw20_staked_balances_voting_contract()); +// let core_contract_id = app.store_code(cw_core_contract()); + +// let instantiate_core = dao_interface::msg::InstantiateMsg { +// admin: None, +// name: "DAO DAO".to_string(), +// description: "A DAO that builds DAOs".to_string(), +// image_url: None, +// dao_uri: None, +// automatically_add_cw20s: true, +// automatically_add_cw721s: false, +// voting_module_instantiate_info: ModuleInstantiateInfo { +// code_id: staked_balances_voting_id, +// msg: to_json_binary(&dao_voting_cw20_staked::msg::InstantiateMsg { +// active_threshold: None, +// token_info: dao_voting_cw20_staked::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: cw20_stake_id, +// unstaking_duration: Some(Duration::Height(6)), +// initial_dao_balance: None, +// }, +// }) +// .unwrap(), +// admin: None, +// funds: vec![], +// label: "DAO DAO voting module".to_string(), +// }, +// proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { +// code_id: v1_proposal_single_code, +// msg: to_json_binary(&instantiate).unwrap(), +// admin: Some(Admin::CoreModule {}), +// funds: vec![], +// label: "DAO DAO governance module.".to_string(), +// }], +// initial_items: None, +// }; + +// let core_addr = app +// .instantiate_contract( +// core_contract_id, +// Addr::unchecked(CREATOR_ADDR), +// &instantiate_core, +// &[], +// "DAO DAO", +// None, +// ) +// .unwrap(); + +// let core_state: dao_interface::query::DumpStateResponse = app +// .wrap() +// .query_wasm_smart( +// core_addr.clone(), +// &dao_interface::msg::QueryMsg::DumpState {}, +// ) +// .unwrap(); +// let voting_module = core_state.voting_module; + +// let staking_contract: Addr = app +// .wrap() +// .query_wasm_smart( +// voting_module.clone(), +// &dao_voting_cw20_staked::msg::QueryMsg::StakingContract {}, +// ) +// .unwrap(); +// let token_contract: Addr = app +// .wrap() +// .query_wasm_smart( +// voting_module, +// &dao_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_json_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), +// }, +// &[], +// ) +// .unwrap(); +// } + +// // Update the block so that those staked balances appear. +// app.update_block(|block| block.height += 1); + +// let proposal_module = query_single_proposal_module(&app, &core_addr); + +// // Make a proposal so we can test that migration doesn't work with +// // open proposals that have deposits. +// mint_cw20s(&mut app, &token_contract, &core_addr, CREATOR_ADDR, 1); +// app.execute_contract( +// Addr::unchecked(CREATOR_ADDR), +// token_contract.clone(), +// &cw20::Cw20ExecuteMsg::IncreaseAllowance { +// spender: proposal_module.to_string(), +// amount: Uint128::new(1), +// expires: None, +// }, +// &[], +// ) +// .unwrap(); +// app.execute_contract( +// Addr::unchecked(CREATOR_ADDR), +// proposal_module.clone(), +// &v1::msg::ExecuteMsg::Propose { +// title: "title".to_string(), +// description: "description".to_string(), +// msgs: vec![], +// }, +// &[], +// ) +// .unwrap(); + +// let v2_proposal_single = app.store_code(proposal_single_contract()); +// let pre_propose_single = app.store_code(pre_propose_single_contract()); + +// // Attempt to migrate. This will fail as there is a pending +// // proposal. +// let migrate_msg = MigrateMsg::FromV2 { timelock: None }; +// let err: ContractError = app +// .execute( +// core_addr.clone(), +// CosmosMsg::Wasm(WasmMsg::Migrate { +// contract_addr: proposal_module.to_string(), +// new_code_id: v2_proposal_single, +// msg: to_json_binary(&migrate_msg).unwrap(), +// }), +// ) +// .unwrap_err() +// .downcast() +// .unwrap(); +// assert!(matches!(err, ContractError::PendingProposals {})); + +// // Vote on and close the pending proposal. +// vote_on_proposal(&mut app, &proposal_module, CREATOR_ADDR, 1, Vote::No); +// close_proposal(&mut app, &proposal_module, CREATOR_ADDR, 1); + +// // Now we can migrate! +// app.execute( +// core_addr.clone(), +// CosmosMsg::Wasm(WasmMsg::Migrate { +// contract_addr: proposal_module.to_string(), +// new_code_id: v2_proposal_single, +// msg: to_json_binary(&migrate_msg).unwrap(), +// }), +// ) +// .unwrap(); + +// let new_config = query_proposal_config(&app, &proposal_module); +// assert_eq!( +// new_config, +// Config { +// timelock: None, +// threshold: Threshold::AbsolutePercentage { +// percentage: PercentageThreshold::Majority {} +// }, +// max_voting_period: Duration::Height(6), +// min_voting_period: None, +// only_members_execute: false, +// allow_revoting: false, +// dao: core_addr.clone(), +// close_proposal_on_execution_failure: true, +// } +// ); + +// // We can not migrate more than once. +// let err: ContractError = app +// .execute( +// core_addr.clone(), +// CosmosMsg::Wasm(WasmMsg::Migrate { +// contract_addr: proposal_module.to_string(), +// new_code_id: v2_proposal_single, +// msg: to_json_binary(&migrate_msg).unwrap(), +// }), +// ) +// .unwrap_err() +// .downcast() +// .unwrap(); +// assert!(matches!(err, ContractError::AlreadyMigrated {})); + +// // Make sure we can still query for ballots (rationale works post +// // migration). +// let vote = query_vote(&app, &proposal_module, CREATOR_ADDR, 1); +// assert_eq!( +// vote.vote.unwrap(), +// VoteInfo { +// voter: Addr::unchecked(CREATOR_ADDR), +// vote: Vote::No, +// power: Uint128::new(100), +// rationale: None +// } +// ); + +// let proposal_creation_policy = query_creation_policy(&app, &proposal_module); + +// // Check that a new creation policy has been birthed. +// let pre_propose = match proposal_creation_policy { +// ProposalCreationPolicy::Anyone {} => panic!("expected a pre-propose module"), +// ProposalCreationPolicy::Module { addr } => addr, +// }; +// let pre_propose_config = query_pre_proposal_single_config(&app, &pre_propose); +// assert_eq!( +// pre_propose_config, +// cppbps::Config { +// open_proposal_submission: false, +// deposit_info: Some(CheckedDepositInfo { +// denom: CheckedDenom::Cw20(token_contract.clone()), +// amount: Uint128::new(1), +// refund_policy: dao_voting::deposit::DepositRefundPolicy::OnlyPassed, +// }) +// } +// ); + +// // Make sure we can still make a proposal and vote on it. +// mint_cw20s(&mut app, &token_contract, &core_addr, CREATOR_ADDR, 1); +// let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); +// vote_on_proposal( +// &mut app, +// &proposal_module, +// CREATOR_ADDR, +// proposal_id, +// Vote::Yes, +// ); +// execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); +// let proposal = query_proposal(&app, &proposal_module, proposal_id); +// assert_eq!(proposal.proposal.status, Status::Executed); +// } // - Make a proposal that will fail to execute. // - Verify that it goes to execution failed and that proposal @@ -1913,6 +3207,7 @@ fn test_execution_failed() { core_addr, proposal_module.clone(), &ExecuteMsg::UpdateConfig { + veto: None, threshold: config.threshold, max_voting_period: config.max_voting_period, min_voting_period: config.min_voting_period, @@ -1984,6 +3279,7 @@ fn test_reply_proposal_mock() { total_power: Uint128::new(100_000_000), msgs: vec![], status: Status::Open, + veto: None, votes: Votes::zero(), }, ) diff --git a/contracts/test/dao-proposal-hook-counter/src/tests.rs b/contracts/test/dao-proposal-hook-counter/src/tests.rs index fddc0f104..9ca8effed 100644 --- a/contracts/test/dao-proposal-hook-counter/src/tests.rs +++ b/contracts/test/dao-proposal-hook-counter/src/tests.rs @@ -155,6 +155,7 @@ fn test_counters() { allow_revoting: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, close_proposal_on_execution_failure: true, + veto: None, }; let governance_addr = diff --git a/contracts/voting/dao-voting-cw721-staked/Cargo.toml b/contracts/voting/dao-voting-cw721-staked/Cargo.toml index 1191b5a85..a3ad240f8 100644 --- a/contracts/voting/dao-voting-cw721-staked/Cargo.toml +++ b/contracts/voting/dao-voting-cw721-staked/Cargo.toml @@ -47,6 +47,6 @@ dao-proposal-single = { workspace = true } dao-proposal-hook-counter = { workspace = true } dao-test-custom-factory = { workspace = true } dao-testing = { workspace = true, features = ["test-tube"] } -osmosis-std = { workpsace = true } +osmosis-std = { workspace = true } osmosis-test-tube = { workspace = true } serde = { workspace = true } diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/integration_tests.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/integration_tests.rs index c85c2559d..aedf0ca32 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/integration_tests.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/integration_tests.rs @@ -112,6 +112,7 @@ fn test_full_integration_with_factory() { only_members_execute: true, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }) .unwrap(), admin: Some(Admin::CoreModule {}), diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/test_tube_env.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/test_tube_env.rs index b3dcae089..c9d462213 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/test_tube_env.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/test_tube_env.rs @@ -61,7 +61,6 @@ impl TestEnvBuilder { let accounts = app .init_accounts(&[Coin::new(1000000000000000u128, "uosmo")], 10) .unwrap(); - // Upload all needed code ids let vp_contract_id = Cw721VotingContract::upload(app, &accounts[0]).unwrap(); let proposal_single_id = DaoProposalSingle::upload(app, &accounts[0]).unwrap(); @@ -137,6 +136,7 @@ impl TestEnvBuilder { only_members_execute: true, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }) .unwrap(), admin: Some(Admin::CoreModule {}), diff --git a/contracts/voting/dao-voting-token-staked/Cargo.toml b/contracts/voting/dao-voting-token-staked/Cargo.toml index 5a66b2eb3..8df86dfe2 100644 --- a/contracts/voting/dao-voting-token-staked/Cargo.toml +++ b/contracts/voting/dao-voting-token-staked/Cargo.toml @@ -47,6 +47,6 @@ dao-proposal-single = { workspace = true } dao-proposal-hook-counter = { workspace = true } dao-test-custom-factory = { workspace = true } dao-testing = { workspace = true, features = ["test-tube"] } -osmosis-std = { workpsace = true } +osmosis-std = { workspace = true } osmosis-test-tube = { workspace = true } serde = { workspace = true } diff --git a/contracts/voting/dao-voting-token-staked/src/tests/mod.rs b/contracts/voting/dao-voting-token-staked/src/tests/mod.rs index 7d9dc1531..d4cde99cd 100644 --- a/contracts/voting/dao-voting-token-staked/src/tests/mod.rs +++ b/contracts/voting/dao-voting-token-staked/src/tests/mod.rs @@ -2,7 +2,7 @@ // Most coverage lives here mod multitest; -// Integrationg tests using an actual chain binary, requires +// Integration tests using an actual chain binary, requires // the "test-tube" feature to be enabled // cargo test --features test-tube #[cfg(test)] diff --git a/contracts/voting/dao-voting-token-staked/src/tests/test_tube/integration_tests.rs b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/integration_tests.rs index e98def001..77effcda6 100644 --- a/contracts/voting/dao-voting-token-staked/src/tests/test_tube/integration_tests.rs +++ b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/integration_tests.rs @@ -1,3 +1,8 @@ +use crate::{ + msg::{ExecuteMsg, InstantiateMsg, QueryMsg, TokenInfo}, + tests::test_tube::test_env::TokenVotingContract, + ContractError, +}; use cosmwasm_std::{to_json_binary, Addr, Coin, Decimal, Uint128, WasmMsg}; use cw_ownable::Ownership; use cw_tokenfactory_issuer::msg::{DenomUnit, QueryMsg as IssuerQueryMsg}; @@ -17,12 +22,6 @@ use osmosis_test_tube::{ RunnerError, }; -use crate::{ - msg::{ExecuteMsg, InstantiateMsg, QueryMsg, TokenInfo}, - tests::test_tube::test_env::TokenVotingContract, - ContractError, -}; - use super::test_env::{TestEnv, TestEnvBuilder, DENOM}; #[test] @@ -397,6 +396,7 @@ fn test_factory() { only_members_execute: true, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }) .unwrap(), admin: Some(Admin::CoreModule {}), @@ -506,6 +506,7 @@ fn test_factory_funds_pass_through() { only_members_execute: true, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }) .unwrap(), admin: Some(Admin::CoreModule {}), @@ -631,6 +632,7 @@ fn test_factory_no_callback() { only_members_execute: true, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }) .unwrap(), admin: Some(Admin::CoreModule {}), @@ -665,7 +667,6 @@ fn test_factory_wrong_callback() { } = env.full_dao_setup(&app); let factory_addr = custom_factory.unwrap().contract_addr.to_string(); - // Instantiate a new voting contract using the factory pattern let msg = dao_interface::msg::InstantiateMsg { dao_uri: None, @@ -712,6 +713,7 @@ fn test_factory_wrong_callback() { only_members_execute: true, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }) .unwrap(), admin: Some(Admin::CoreModule {}), diff --git a/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs index 20569490f..3c93c9ee6 100644 --- a/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs +++ b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs @@ -247,6 +247,7 @@ impl TestEnvBuilder { only_members_execute: true, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }) .unwrap(), admin: Some(Admin::CoreModule {}), diff --git a/packages/dao-hooks/Cargo.toml b/packages/dao-hooks/Cargo.toml index 45ea0f640..7e9a4f0eb 100644 --- a/packages/dao-hooks/Cargo.toml +++ b/packages/dao-hooks/Cargo.toml @@ -12,4 +12,5 @@ cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } cw4 = { workspace = true } cw-hooks = { workspace = true } +dao-pre-propose-base = { workspace = true } dao-voting = { workspace = true } diff --git a/packages/dao-hooks/src/all_hooks.rs b/packages/dao-hooks/src/all_hooks.rs index 3dfe9ea4a..580e773f1 100644 --- a/packages/dao-hooks/src/all_hooks.rs +++ b/packages/dao-hooks/src/all_hooks.rs @@ -2,7 +2,7 @@ use cosmwasm_schema::cw_serde; use cw4::MemberChangedHookMsg; use crate::nft_stake::NftStakeChangedHookMsg; -use crate::proposal::ProposalHookMsg; +use crate::proposal::{PreProposeHookMsg, ProposalHookMsg}; use crate::stake::StakeChangedHookMsg; use crate::vote::VoteHookMsg; @@ -14,6 +14,8 @@ pub enum DaoHooks { MemberChangedHook(MemberChangedHookMsg), /// Called when NFTs are staked or unstaked. NftStakeChangeHook(NftStakeChangedHookMsg), + /// Pre-propose hooks + PreProposeHook(PreProposeHookMsg), /// Called when a proposal status changes. ProposalHook(ProposalHookMsg), /// Called when tokens are staked or unstaked. diff --git a/packages/dao-hooks/src/proposal.rs b/packages/dao-hooks/src/proposal.rs index 6ccc7bd34..c98cfdc45 100644 --- a/packages/dao-hooks/src/proposal.rs +++ b/packages/dao-hooks/src/proposal.rs @@ -1,7 +1,11 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{to_json_binary, StdResult, Storage, SubMsg, WasmMsg}; +use cosmwasm_std::{to_json_binary, Empty, StdResult, Storage, SubMsg, WasmMsg}; use cw_hooks::Hooks; -use dao_voting::reply::mask_proposal_hook_index; +use dao_voting::{ + pre_propose::ProposalCreationPolicy, + reply::{failed_pre_propose_module_hook_id, mask_proposal_hook_index}, + status::Status, +}; /// An enum representing proposal hook messages. /// Either a new propsoal hook, fired when a new proposal is created, @@ -88,6 +92,37 @@ pub fn proposal_status_changed_hooks( Ok(messages) } +/// Message type used for firing hooks to a proposal module's pre-propose +/// module, if one is installed. +pub type PreProposeHookMsg = dao_pre_propose_base::msg::ExecuteMsg; + +/// Adds prepropose / deposit module hook which will handle deposit refunds. +pub fn proposal_completed_hooks( + proposal_creation_policy: ProposalCreationPolicy, + proposal_id: u64, + new_status: Status, +) -> StdResult> { + let mut hooks: Vec = vec![]; + match proposal_creation_policy { + ProposalCreationPolicy::Anyone {} => (), + ProposalCreationPolicy::Module { addr } => { + let msg = to_json_binary(&PreProposeHookMsg::ProposalCompletedHook { + proposal_id, + new_status, + })?; + hooks.push(SubMsg::reply_on_error( + WasmMsg::Execute { + contract_addr: addr.into_string(), + msg, + funds: vec![], + }, + failed_pre_propose_module_hook_id(), + )); + } + }; + Ok(hooks) +} + #[cw_serde] pub enum ProposalHookExecuteMsg { ProposalHook(ProposalHookMsg), diff --git a/packages/dao-pre-propose-base/Cargo.toml b/packages/dao-pre-propose-base/Cargo.toml index a0be0d404..c063d34f9 100644 --- a/packages/dao-pre-propose-base/Cargo.toml +++ b/packages/dao-pre-propose-base/Cargo.toml @@ -24,7 +24,6 @@ cw-denom = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } cw-hooks = { workspace = true } -dao-hooks = { workspace = true } dao-interface = { workspace = true } dao-voting = { workspace = true } serde = { workspace = true } diff --git a/packages/dao-pre-propose-base/src/error.rs b/packages/dao-pre-propose-base/src/error.rs index 8ccfbb140..127996166 100644 --- a/packages/dao-pre-propose-base/src/error.rs +++ b/packages/dao-pre-propose-base/src/error.rs @@ -38,8 +38,8 @@ pub enum PreProposeError { #[error("Nothing to withdraw")] NothingToWithdraw {}, - #[error("Proposal status ({status}) not closed or executed")] - NotClosedOrExecuted { status: Status }, + #[error("Proposal status ({status}) is not completed")] + NotCompleted { status: Status }, #[error("Proposal not found")] ProposalNotFound {}, diff --git a/packages/dao-pre-propose-base/src/execute.rs b/packages/dao-pre-propose-base/src/execute.rs index 8bbb51929..64e0ed3d6 100644 --- a/packages/dao-pre-propose-base/src/execute.rs +++ b/packages/dao-pre-propose-base/src/execute.rs @@ -286,19 +286,28 @@ where // bizare has happened. In that event, this message errors // which ought to cause the proposal module to remove this // module and open proposal submission to anyone. - if new_status != Status::Closed && new_status != Status::Executed { - return Err(PreProposeError::NotClosedOrExecuted { status: new_status }); + if new_status != Status::Closed + && new_status != Status::Executed + && new_status != Status::Vetoed + { + return Err(PreProposeError::NotCompleted { status: new_status }); } match self.deposits.may_load(deps.storage, id)? { Some((deposit_info, proposer)) => { let messages = if let Some(ref deposit_info) = deposit_info { - // Refund can be issued if proposal if it is going to - // closed or executed. - let should_refund_to_proposer = (new_status == Status::Closed - && deposit_info.refund_policy == DepositRefundPolicy::Always) - || (new_status == Status::Executed - && deposit_info.refund_policy != DepositRefundPolicy::Never); + // Determine if refund can be issued + let should_refund_to_proposer = + match (new_status, deposit_info.clone().refund_policy) { + // If policy is refund only passed props, refund for executed status + (Status::Executed, DepositRefundPolicy::OnlyPassed) => true, + // Don't refund other statuses for OnlyPassed policy + (_, DepositRefundPolicy::OnlyPassed) => false, + // Refund if the refund policy is always refund + (_, DepositRefundPolicy::Always) => true, + // Don't refund if the refund is never refund + (_, DepositRefundPolicy::Never) => false, + }; if should_refund_to_proposer { deposit_info.get_return_deposit_message(&proposer)? diff --git a/packages/dao-pre-propose-base/src/tests.rs b/packages/dao-pre-propose-base/src/tests.rs index c2f29284e..a6b12a74d 100644 --- a/packages/dao-pre-propose-base/src/tests.rs +++ b/packages/dao-pre-propose-base/src/tests.rs @@ -38,7 +38,7 @@ fn test_completed_hook_status_invariant() { assert_eq!( res.unwrap_err(), - PreProposeError::NotClosedOrExecuted { + PreProposeError::NotCompleted { status: Status::Passed } ); diff --git a/packages/dao-testing/Cargo.toml b/packages/dao-testing/Cargo.toml index 9698f9e24..677709d53 100644 --- a/packages/dao-testing/Cargo.toml +++ b/packages/dao-testing/Cargo.toml @@ -33,7 +33,7 @@ osmosis-test-tube = { workspace = true } rand = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -token-bindings = { workpsace = true } +token-bindings = { workspace = true } cw-core-v1 = { workspace = true, features = ["library"] } cw-hooks = { workspace = true } diff --git a/packages/dao-voting/src/lib.rs b/packages/dao-voting/src/lib.rs index 7395691b2..747274849 100644 --- a/packages/dao-voting/src/lib.rs +++ b/packages/dao-voting/src/lib.rs @@ -9,4 +9,5 @@ pub mod proposal; pub mod reply; pub mod status; pub mod threshold; +pub mod veto; pub mod voting; diff --git a/packages/dao-voting/src/status.rs b/packages/dao-voting/src/status.rs index 3b75af978..b2881faaa 100644 --- a/packages/dao-voting/src/status.rs +++ b/packages/dao-voting/src/status.rs @@ -1,4 +1,5 @@ use cosmwasm_schema::cw_serde; +use cw_utils::Expiration; #[cw_serde] #[derive(Copy)] @@ -16,6 +17,11 @@ pub enum Status { Closed, /// The proposal's execution failed. ExecutionFailed, + /// The proposal is timelocked. Only the configured vetoer + /// can execute or veto until the timelock expires. + VetoTimelock { expiration: Expiration }, + /// The proposal has been vetoed. + Vetoed, } impl std::fmt::Display for Status { @@ -27,6 +33,10 @@ impl std::fmt::Display for Status { Status::Executed => write!(f, "executed"), Status::Closed => write!(f, "closed"), Status::ExecutionFailed => write!(f, "execution_failed"), + Status::VetoTimelock { expiration } => { + write!(f, "veto_timelock_until_{:?}", expiration) + } + Status::Vetoed => write!(f, "vetoed"), } } } diff --git a/packages/dao-voting/src/veto.rs b/packages/dao-voting/src/veto.rs new file mode 100644 index 000000000..fe5b77946 --- /dev/null +++ b/packages/dao-voting/src/veto.rs @@ -0,0 +1,91 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Deps, MessageInfo, StdError}; +use cw_utils::Duration; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum VetoError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Proposal is {status} and thus is unable to be vetoed.")] + InvalidProposalStatus { status: String }, + + #[error("Early execution for timelocked proposals is not enabled. Proposal can not be executed before the timelock delay has expired.")] + NoEarlyExecute {}, + + #[error("Veto is not enabled for this contract.")] + NoVetoConfiguration {}, + + #[error("Vetoing before a proposal passes is not enabled.")] + NoVetoBeforePassed {}, + + #[error("The proposal is timelocked and cannot be executed.")] + Timelocked {}, + + #[error("The veto timelock duration has expired.")] + TimelockExpired {}, + + #[error("The veto timelock duration must have the same units as the max_voting_period of the proposal (height or time).")] + TimelockDurationUnitMismatch {}, + + #[error("Only vetoer can veto a proposal.")] + Unauthorized {}, +} + +#[cw_serde] +pub struct VetoConfig { + /// The time duration to lock a proposal for after its expiration to allow + /// the vetoer to veto. + pub timelock_duration: Duration, + /// The address able to veto proposals. + pub vetoer: String, + /// Whether or not the vetoer can execute a proposal early before the + /// timelock duration has expired + pub early_execute: bool, + /// Whether or not the vetoer can veto a proposal before it passes. + pub veto_before_passed: bool, +} + +impl VetoConfig { + pub fn validate(&self, deps: &Deps, max_voting_period: &Duration) -> Result<(), VetoError> { + // Validate vetoer address. + deps.api.addr_validate(&self.vetoer)?; + + // Validate duration units match voting period. + match (self.timelock_duration, max_voting_period) { + (Duration::Time(_), Duration::Time(_)) => (), + (Duration::Height(_), Duration::Height(_)) => (), + _ => return Err(VetoError::TimelockDurationUnitMismatch {}), + }; + + Ok(()) + } + + /// Whether early execute is enabled + pub fn check_early_execute_enabled(&self) -> Result<(), VetoError> { + if self.early_execute { + Ok(()) + } else { + Err(VetoError::NoEarlyExecute {}) + } + } + + /// Checks whether the message sender is the vetoer. + pub fn check_is_vetoer(&self, info: &MessageInfo) -> Result<(), VetoError> { + if self.vetoer == info.sender { + Ok(()) + } else { + Err(VetoError::Unauthorized {}) + } + } + + /// Checks whether veto_before_passed is enabled, errors if not + pub fn check_veto_before_passed_enabled(&self) -> Result<(), VetoError> { + if self.veto_before_passed { + Ok(()) + } else { + Err(VetoError::NoVetoBeforePassed {}) + } + } +}