diff --git a/Cargo.lock b/Cargo.lock index b1e7d67d1..3215472c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,273 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "abstract-cw-multi-test" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c77f8d4bac08f74fbc4fce8943cb2d35e742682b6cae8cb65555d6cd3830feb" +dependencies = [ + "anyhow", + "bech32 0.11.0", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw20-ics20", + "derivative", + "hex", + "itertools 0.12.1", + "log", + "prost 0.12.3", + "schemars", + "serde", + "serde_json", + "sha2 0.10.8", + "thiserror", +] + +[[package]] +name = "abstract-cw-plus-interface" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7441425a805439500492977107d154af02b1702aa044945775c245d0b3469968" +dependencies = [ + "abstract-cw1", + "abstract-cw1-subkeys", + "abstract-cw1-whitelist", + "abstract-cw20-base", + "abstract-cw20-ics20", + "abstract-cw3-fixed-multisig", + "abstract-cw3-flex-multisig", + "abstract-cw4-group", + "abstract-cw4-stake", + "cosmwasm-std", + "cw-orch", +] + +[[package]] +name = "abstract-cw1" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0895c076ab6a5165133a453f983ec9ccc9b6c41de256b6eb74e523eb555b3ebb" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "abstract-cw1-subkeys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab08cdd6008afa38a52427943bf4aef9541bde78cc9c14849a53ad2608a1161e" +dependencies = [ + "abstract-cw1", + "abstract-cw1-whitelist", + "abstract-cw2", + "cosmwasm-schema", + "cosmwasm-std", + "cw-orch", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "schemars", + "semver", + "serde", + "thiserror", +] + +[[package]] +name = "abstract-cw1-whitelist" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171a0b5b3694627cf0fa554500d72431169d4013fffd14650d2b7d660230a205" +dependencies = [ + "abstract-cw1", + "abstract-cw2", + "cosmwasm-schema", + "cosmwasm-std", + "cw-orch", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "abstract-cw2" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "945af4c176b4539be2a74c06aa166287ba964ab58aec98c644addd812431f141" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "abstract-cw20" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d5e4b8084c3a2b3e42502e6c4fe3ed985dc72e86eb612bcc527f4a0443fa42" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-orch", + "cw-utils 1.0.3", + "schemars", + "serde", +] + +[[package]] +name = "abstract-cw20-base" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d300dec7d602e00841c5ab6fe598d4d290bab32e489c6885c607633c4f3fe67" +dependencies = [ + "abstract-cw2", + "abstract-cw20", + "cosmwasm-schema", + "cosmwasm-std", + "cw-orch", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "schemars", + "semver", + "serde", + "thiserror", +] + +[[package]] +name = "abstract-cw20-ics20" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "027678ddb0e62b4aba5f0167d2b0a3ec0182e1e32c47759be7e30b56775598ee" +dependencies = [ + "abstract-cw2", + "abstract-cw20", + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers 1.1.2", + "cw-orch", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "schemars", + "semver", + "serde", + "thiserror", +] + +[[package]] +name = "abstract-cw3" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c080cc760333d1d3477857aeac19aa7e6e661f1e58d04a7a78212913d49bf517" +dependencies = [ + "abstract-cw20", + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 1.0.3", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "abstract-cw3-fixed-multisig" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1882e05bef33bd1c6b25e735eda8a23332a78c4df0b24a18ca56a8ca8ed6f222" +dependencies = [ + "abstract-cw2", + "abstract-cw3", + "cosmwasm-schema", + "cosmwasm-std", + "cw-orch", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "abstract-cw3-flex-multisig" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92379f3e7c467f081312d6953eb8d300456efa352c9f7c5ef095ad99083d92db" +dependencies = [ + "abstract-cw2", + "abstract-cw20", + "abstract-cw3", + "abstract-cw3-fixed-multisig", + "abstract-cw4", + "cosmwasm-schema", + "cosmwasm-std", + "cw-orch", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "abstract-cw4" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aacb0124dce37ee6f2b5636684285bcbaa65a1678980f95ea76366ab74a8912" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "schemars", + "serde", +] + +[[package]] +name = "abstract-cw4-group" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0af5ef484ba1d48fee8485452c81ac3465ba16a5941db90bda4dd6b58b50a9a6" +dependencies = [ + "abstract-cw2", + "abstract-cw4", + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers 1.1.2", + "cw-orch", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "abstract-cw4-stake" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1eb9985e8b752396a2c5d8fde8ebf65ea81070a95f167a3d31af0746f8e4b4e" +dependencies = [ + "abstract-cw2", + "abstract-cw20", + "abstract-cw4", + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers 1.1.2", + "cw-orch", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "addr2line" version = "0.22.0" @@ -68,7 +335,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -79,7 +346,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -184,6 +451,12 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + [[package]] name = "bindgen" version = "0.68.1" @@ -203,7 +476,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.72", + "syn 2.0.74", "which", ] @@ -333,18 +606,21 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" dependencies = [ "serde", ] [[package]] name = "cc" -version = "1.1.6" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" +checksum = "5fb8dd288a69fc53a1996d7ecfbf4a20d59065bff137ce7e56bbd620de191189" +dependencies = [ + "shlex", +] [[package]] name = "cexpr" @@ -406,6 +682,15 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -418,9 +703,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cosm-orc" @@ -489,6 +774,17 @@ dependencies = [ "tonic 0.9.2", ] +[[package]] +name = "cosmos-sdk-proto" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e23f6ab56d5f031cde05b8b82a5fefd3a1a223595c79e32317a97189e612bc" +dependencies = [ + "prost 0.12.3", + "prost-types 0.12.3", + "tendermint-proto 0.35.0", +] + [[package]] name = "cosmrs" version = "0.9.0" @@ -533,32 +829,31 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.5.5" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd50718a2b6830ce9eb5d465de5a018a12e71729d66b70807ce97e6dd14f931d" +checksum = "0f862b355f7e47711e0acfe6af92cb3fd8fd5936b66a9eaa338b51edabd1e77d" dependencies = [ "digest 0.10.7", - "ecdsa 0.16.9", "ed25519-zebra", - "k256 0.13.1", + "k256 0.13.3", "rand_core 0.6.4", "thiserror", ] [[package]] name = "cosmwasm-derive" -version = "1.5.5" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "242e98e7a231c122e08f300d9db3262d1007b51758a8732cd6210b3e9faa4f3a" +checksum = "cd85de6467cd1073688c86b39833679ae6db18cf4771471edd9809f15f1679f1" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.5.5" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7879036156092ad1c22fe0d7316efc5a5eceec2bc3906462a2560215f2a2f929" +checksum = "5b4cd28147a66eba73720b47636a58097a979ad8c8bfdb4ed437ebcbfe362576" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -569,9 +864,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.5.5" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb57855fbfc83327f8445ae0d413b1a05ac0d68c396ab4d122b2abd7bb82cb6" +checksum = "9acd45c63d41bc9b16bc6dc7f6bd604a8c2ad29ce96c8f3c96d7fc8ef384392e" dependencies = [ "proc-macro2", "quote", @@ -580,12 +875,12 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.5.5" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c1556156fdf892a55cced6115968b961eaaadd6f724a2c2cb7d1e168e32dd3" +checksum = "2685c2182624b2e9e17f7596192de49a3f86b7a0c9a5f6b25c1df5e24592e836" dependencies = [ "base64 0.21.7", - "bech32", + "bech32 0.9.1", "bnum", "cosmwasm-crypto", "cosmwasm-derive", @@ -612,9 +907,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" dependencies = [ "libc", ] @@ -688,7 +983,7 @@ dependencies = [ name = "cw-admin-factory" version = "2.5.0" dependencies = [ - "bech32", + "bech32 0.9.1", "cosmwasm-schema", "cosmwasm-std", "cw-admin-factory", @@ -891,7 +1186,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc392a5cb7e778e3f90adbf7faa43c4db7f35b6623224b08886d796718edb875" dependencies = [ "anyhow", - "bech32", + "bech32 0.9.1", "cosmwasm-std", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -904,6 +1199,96 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw-orch" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1ddc937c28c59ccf2765fa05ddc0437644d3b283408a7cc64f7b371b0b9309" +dependencies = [ + "anyhow", + "cosmwasm-std", + "cw-orch-contract-derive", + "cw-orch-core", + "cw-orch-fns-derive", + "cw-orch-mock", + "cw-orch-traits", + "cw-utils 1.0.3", + "hex", + "log", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw-orch-contract-derive" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc8ba75692fc7bd30e91c78fad2dc208a738e4e6ea26b232f9352c320e35543" +dependencies = [ + "convert_case", + "quote", + "syn 2.0.74", +] + +[[package]] +name = "cw-orch-core" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5abd640f67f655411542a3c148769270c7a9e63d4097c2dc4a1f6edf23b7a9b4" +dependencies = [ + "abstract-cw-multi-test", + "anyhow", + "cosmos-sdk-proto 0.21.1", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "dirs", + "log", + "serde", + "serde_json", + "sha2 0.10.8", + "thiserror", +] + +[[package]] +name = "cw-orch-fns-derive" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9acb7a15bfacc52abdf312a9fffb139883c1effb6ea7e645cd39580a8527463" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cw-orch-mock" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae9536620b86ee78c2729fd8449538feb4f6257a9809c72c5f9e461e720cf3b" +dependencies = [ + "abstract-cw-multi-test", + "cosmwasm-std", + "cw-orch-core", + "cw-utils 1.0.3", + "log", + "serde", + "sha2 0.10.8", +] + +[[package]] +name = "cw-orch-traits" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5959ce29e9d8a52594b47933a0a2736ea94dd9bf5e29b220cbdbe2b097f07c3a" +dependencies = [ + "cw-orch-core", + "prost 0.12.3", + "prost-types 0.12.3", +] + [[package]] name = "cw-ownable" version = "0.5.1" @@ -1339,6 +1724,25 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw20-ics20" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76221201da08fed611c857ea3aa21c031a4a7dc771a8b1750559ca987335dc02" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers 1.1.2", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20 1.1.2", + "schemars", + "semver", + "serde", + "thiserror", +] + [[package]] name = "cw20-stake" version = "0.2.6" @@ -1641,6 +2045,34 @@ dependencies = [ "thiserror", ] +[[package]] +name = "dao-cw-orch" +version = "2.5.0" +dependencies = [ + "cw-orch", + "cw20-stake 2.5.0", + "cw20-stake-external-rewards", + "cw20-stake-reward-distributor", + "dao-dao-core 2.5.0", + "dao-interface 2.5.0", + "dao-pre-propose-approval-single 2.5.0", + "dao-pre-propose-approver", + "dao-pre-propose-multiple 2.5.0", + "dao-pre-propose-single 2.5.0", + "dao-proposal-condorcet", + "dao-proposal-hook-counter", + "dao-proposal-multiple 2.5.0", + "dao-proposal-single 2.5.0", + "dao-proposal-sudo", + "dao-test-custom-factory", + "dao-voting-cw20-balance", + "dao-voting-cw20-staked", + "dao-voting-cw4 2.5.0", + "dao-voting-cw721-roles", + "dao-voting-cw721-staked", + "dao-voting-token-staked", +] + [[package]] name = "dao-cw721-extensions" version = "2.5.0" @@ -1675,10 +2107,12 @@ dependencies = [ name = "dao-dao-core" version = "2.5.0" dependencies = [ + "abstract-cw-plus-interface", "cosmwasm-schema", "cosmwasm-std", "cw-core", "cw-multi-test", + "cw-orch", "cw-paginate-storage 2.5.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -1687,6 +2121,7 @@ dependencies = [ "cw20-base 1.1.2", "cw721 0.18.0", "cw721-base 0.18.0", + "dao-cw-orch", "dao-dao-macros 2.5.0", "dao-interface 2.5.0", "dao-proposal-sudo", @@ -1767,6 +2202,7 @@ version = "2.5.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", + "cw-orch", "cw-utils 1.0.3", "cw2 1.1.2", "cw20 1.1.2", @@ -2188,6 +2624,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", + "cw-orch", "cw-storage-plus 1.2.0", "cw2 1.1.2", "dao-dao-macros 2.5.0", @@ -2572,6 +3009,27 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dlv-list" version = "0.3.0" @@ -2846,7 +3304,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -2949,7 +3407,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.2.6", + "indexmap 2.4.0", "slab", "tokio", "tokio-util", @@ -3001,6 +3459,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -3179,9 +3643,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -3223,11 +3687,11 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "hermit-abi", + "hermit-abi 0.4.0", "libc", "windows-sys 0.52.0", ] @@ -3241,6 +3705,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.1" @@ -3258,9 +3731,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -3291,9 +3764,9 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" dependencies = [ "cfg-if", "ecdsa 0.16.9", @@ -3340,6 +3813,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -3393,13 +3876,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi 0.3.9", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3424,22 +3908,23 @@ dependencies = [ ] [[package]] -name = "num-traits" -version = "0.2.19" +name = "num-derive" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ - "autocfg", + "proc-macro2", + "quote", + "syn 2.0.74", ] [[package]] -name = "num_cpus" -version = "1.16.0" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "hermit-abi", - "libc", + "autocfg", ] [[package]] @@ -3453,9 +3938,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.1" +version = "0.36.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" dependencies = [ "memchr", ] @@ -3507,6 +3992,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-multimap" version = "0.4.3" @@ -3655,7 +4146,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3686,7 +4177,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3723,9 +4214,12 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "prettyplease" @@ -3734,7 +4228,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3798,10 +4292,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -3867,11 +4361,22 @@ dependencies = [ "getrandom", ] +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" -version = "1.10.5" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -4074,7 +4579,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -4146,9 +4651,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.204" +version = "1.0.207" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2" dependencies = [ "serde_derive", ] @@ -4182,13 +4687,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.207" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -4199,16 +4704,17 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -4221,7 +4727,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -4230,7 +4736,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.4.0", "itoa", "ryu", "serde", @@ -4445,9 +4951,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" dependencies = [ "proc-macro2", "quote", @@ -4558,7 +5064,7 @@ checksum = "68ce80bf536476db81ecc9ebab834dc329c9c1509a694f211a73858814bfe023" dependencies = [ "bytes", "flex-error", - "num-derive", + "num-derive 0.3.3", "num-traits", "prost 0.11.9", "prost-types 0.11.9", @@ -4576,7 +5082,7 @@ checksum = "974d6330a19dfa6720e9f663fc59101d207a817db3f9c730d3f31caaa565b574" dependencies = [ "bytes", "flex-error", - "num-derive", + "num-derive 0.3.3", "num-traits", "prost 0.11.9", "prost-types 0.11.9", @@ -4594,7 +5100,7 @@ checksum = "c0cec054567d16d85e8c3f6a3139963d1a66d9d3051ed545d31562550e9bcc3d" dependencies = [ "bytes", "flex-error", - "num-derive", + "num-derive 0.3.3", "num-traits", "prost 0.11.9", "prost-types 0.11.9", @@ -4604,6 +5110,24 @@ dependencies = [ "time", ] +[[package]] +name = "tendermint-proto" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff525d5540a9fc535c38dc0d92a98da3ee36fcdfbda99cecb9f3cce5cd4d41d7" +dependencies = [ + "bytes", + "flex-error", + "num-derive 0.4.2", + "num-traits", + "prost 0.12.3", + "prost-types 0.12.3", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + [[package]] name = "tendermint-rpc" version = "0.23.9" @@ -4698,7 +5222,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d506c7664333e246f564949bee4ed39062aa0f11918e6f5a95f553cdad65c274" dependencies = [ "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -4734,7 +5258,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -4771,19 +5295,18 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.1" +version = "1.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" +checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "pin-project-lite", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -4798,13 +5321,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -4933,15 +5456,15 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -4962,7 +5485,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] [[package]] @@ -5023,6 +5546,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -5054,9 +5583,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vote-hooks" @@ -5097,34 +5626,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5132,28 +5662,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -5208,11 +5738,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5239,6 +5769,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -5381,6 +5920,27 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.74", +] + [[package]] name = "zeroize" version = "1.8.1" @@ -5398,5 +5958,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.74", ] diff --git a/contracts/dao-dao-core/Cargo.toml b/contracts/dao-dao-core/Cargo.toml index a9ac8dae6..549f93f98 100644 --- a/contracts/dao-dao-core/Cargo.toml +++ b/contracts/dao-dao-core/Cargo.toml @@ -31,8 +31,11 @@ cw-paginate-storage = { workspace = true } cw-core-v1 = { workspace = true, features = ["library"] } [dev-dependencies] +abstract-cw-plus-interface = "2.0.1" cw-multi-test = { workspace = true } +cw-orch = "0.22.2" cw20-base = { workspace = true } cw721-base = { workspace = true } +dao-cw-orch = { path = "../../packages/cw-orch" } dao-proposal-sudo = { workspace = true } dao-voting-cw20-balance = { workspace = true } diff --git a/contracts/dao-dao-core/src/cw_orch_tests.rs b/contracts/dao-dao-core/src/cw_orch_tests.rs new file mode 100644 index 000000000..ebfd8eb58 --- /dev/null +++ b/contracts/dao-dao-core/src/cw_orch_tests.rs @@ -0,0 +1,3068 @@ +use crate::{ + contract::{derive_proposal_module_prefix, migrate, CONTRACT_NAME, CONTRACT_VERSION}, + state::PROPOSAL_MODULES, + ContractError, +}; +use abstract_cw_plus_interface::cw20_base::Cw20Base; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + from_json, + testing::{mock_dependencies, mock_env}, + to_json_binary, Addr, CosmosMsg, Empty, Storage, Uint128, WasmMsg, +}; +use cw2::{set_contract_version, ContractVersion}; +use cw_multi_test::{App, Contract, ContractWrapper, Executor}; +use cw_orch::prelude::*; +use cw_storage_plus::{Item, Map}; +use cw_utils::{Duration, Expiration}; +use dao_cw_orch::{DaoDaoCore, DaoProposalSudo}; +use dao_interface::CoreQueryMsgFns; +use dao_interface::{ + msg::{ExecuteMsg, InitialItem, InstantiateMsg, MigrateMsg, QueryMsg}, + query::{ + AdminNominationResponse, Cw20BalanceResponse, DumpStateResponse, GetItemResponse, + PauseInfoResponse, ProposalModuleCountResponse, SubDao, + }, + state::{Admin, Config, ModuleInstantiateInfo, ProposalModule, ProposalModuleStatus}, + voting::{InfoResponse, VotingPowerAtHeightResponse}, +}; +use dao_proposal_sudo::msg::ExecuteMsgFns as _; + +const CREATOR_ADDR: &str = "creator"; + +fn cw20_contract() -> Box> { + let contract = ContractWrapper::new( + cw20_base::contract::execute, + cw20_base::contract::instantiate, + cw20_base::contract::query, + ); + Box::new(contract) +} + +fn cw721_contract() -> Box> { + let contract = ContractWrapper::new( + cw721_base::entry::execute, + cw721_base::entry::instantiate, + cw721_base::entry::query, + ); + Box::new(contract) +} + +fn sudo_proposal_contract() -> Box> { + let contract = ContractWrapper::new( + dao_proposal_sudo::contract::execute, + dao_proposal_sudo::contract::instantiate, + dao_proposal_sudo::contract::query, + ); + Box::new(contract) +} + +fn cw20_balances_voting() -> Box> { + let contract = ContractWrapper::new( + dao_voting_cw20_balance::contract::execute, + dao_voting_cw20_balance::contract::instantiate, + dao_voting_cw20_balance::contract::query, + ) + .with_reply(dao_voting_cw20_balance::contract::reply); + Box::new(contract) +} + +fn cw_core_contract() -> Box> { + let contract = ContractWrapper::new( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ) + .with_reply(crate::contract::reply) + .with_migrate(crate::contract::migrate); + Box::new(contract) +} + +fn v1_cw_core_contract() -> Box> { + use cw_core_v1::contract; + let contract = ContractWrapper::new(contract::execute, contract::instantiate, contract::query) + .with_reply(contract::reply) + .with_migrate(contract::migrate); + Box::new(contract) +} + +fn test_instantiate_with_n_gov_modules(n: usize) { + let mock = MockBech32::new("mock"); + let cw20 = Cw20Base::new("cw20", mock.clone()); + let gov = DaoDaoCore::new("dao-core", mock.clone()); + cw20.upload().unwrap(); + let cw20_id = cw20.code_id().unwrap(); + gov.upload().unwrap(); + + let cw20_instantiate = cw20_base::msg::InstantiateMsg { + name: "DAO".to_string(), + symbol: "DAO".to_string(), + decimals: 6, + initial_balances: vec![], + mint: None, + marketing: None, + }; + let instantiate = InstantiateMsg { + dao_uri: None, + admin: None, + name: "DAO DAO".to_string(), + description: "A DAO that builds DAOs.".to_string(), + image_url: None, + automatically_add_cw20s: true, + automatically_add_cw721s: true, + voting_module_instantiate_info: ModuleInstantiateInfo { + code_id: cw20_id, + msg: to_json_binary(&cw20_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "voting module".to_string(), + }, + proposal_modules_instantiate_info: (0..n) + .map(|n| ModuleInstantiateInfo { + code_id: cw20_id, + msg: to_json_binary(&cw20_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: format!("governance module {n}"), + }) + .collect(), + initial_items: None, + }; + gov.instantiate(&instantiate, None, None).unwrap(); + + let state = gov.dump_state().unwrap(); + + assert_eq!( + state.config, + Config { + dao_uri: None, + name: "DAO DAO".to_string(), + description: "A DAO that builds DAOs.".to_string(), + image_url: None, + automatically_add_cw20s: true, + automatically_add_cw721s: true, + } + ); + + assert_eq!(state.proposal_modules.len(), n); + + assert_eq!(state.active_proposal_module_count, n as u32); + assert_eq!(state.total_proposal_module_count, n as u32); +} + +#[test] +#[should_panic(expected = "Execution would result in no proposal modules being active.")] +fn test_instantiate_with_zero_gov_modules() { + test_instantiate_with_n_gov_modules(0) +} + +#[test] +fn test_valid_instantiate() { + let module_counts = [1, 2, 200]; + for count in module_counts { + test_instantiate_with_n_gov_modules(count) + } +} + +#[test] +#[should_panic( + expected = "Error parsing into type abstract_cw20_base::msg::InstantiateMsg: Invalid type" +)] +fn test_instantiate_with_submessage_failure() { + let mock = MockBech32::new("mock"); + let cw20 = Cw20Base::new("cw20", mock.clone()); + let gov = DaoDaoCore::new("dao-core", mock.clone()); + cw20.upload().unwrap(); + let cw20_id = cw20.code_id().unwrap(); + gov.upload().unwrap(); + + let cw20_instantiate = cw20_base::msg::InstantiateMsg { + name: "DAO".to_string(), + symbol: "DAO".to_string(), + decimals: 6, + initial_balances: vec![], + mint: None, + marketing: None, + }; + + let mut governance_modules = (0..3) + .map(|n| ModuleInstantiateInfo { + code_id: cw20_id, + msg: to_json_binary(&cw20_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: format!("governance module {n}"), + }) + .collect::>(); + governance_modules.push(ModuleInstantiateInfo { + code_id: cw20_id, + msg: to_json_binary("bad").unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "I have a bad instantiate message".to_string(), + }); + governance_modules.push(ModuleInstantiateInfo { + code_id: cw20_id, + msg: to_json_binary(&cw20_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "Everybody knowing +that goodness is good +makes wickedness." + .to_string(), + }); + + let instantiate = InstantiateMsg { + dao_uri: None, + admin: None, + name: "DAO DAO".to_string(), + description: "A DAO that builds DAOs.".to_string(), + image_url: None, + automatically_add_cw20s: true, + automatically_add_cw721s: true, + voting_module_instantiate_info: ModuleInstantiateInfo { + code_id: cw20_id, + msg: to_json_binary(&cw20_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "voting module".to_string(), + }, + proposal_modules_instantiate_info: governance_modules, + initial_items: None, + }; + + gov.instantiate(&instantiate, None, None).unwrap(); +} + +#[test] +fn test_update_config() -> cw_orch::anyhow::Result<()> { + let mock = MockBech32::new("mock"); + let gov_mod = DaoProposalSudo::new("proposal", mock.clone()); + let gov = DaoDaoCore::new("dao-core", mock.clone()); + gov_mod.upload()?; + let govmod_id = gov_mod.code_id()?; + gov.upload()?; + + let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { + root: mock.sender().to_string(), + }; + + let gov_instantiate = InstantiateMsg { + dao_uri: None, + admin: None, + name: "DAO DAO".to_string(), + description: "A DAO that builds DAOs.".to_string(), + image_url: None, + automatically_add_cw20s: true, + automatically_add_cw721s: true, + voting_module_instantiate_info: ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate)?, + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "voting module".to_string(), + }, + proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate)?, + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "voting module".to_string(), + }], + initial_items: None, + }; + + gov.instantiate(&gov_instantiate, None, None)?; + + let modules = gov.proposal_modules(None, None)?; + assert_eq!(modules.len(), 1); + gov_mod.set_address(&modules[0].clone().address); + + let expected_config = Config { + name: "Root DAO".to_string(), + description: "We love trees and sudo.".to_string(), + image_url: Some("https://moonphase.is/image.svg".to_string()), + automatically_add_cw20s: false, + automatically_add_cw721s: true, + dao_uri: Some("https://daostar.one/EIP".to_string()), + }; + + gov_mod.proposal_execute(vec![WasmMsg::Execute { + contract_addr: gov.address()?.to_string(), + funds: vec![], + msg: to_json_binary(&ExecuteMsg::UpdateConfig { + config: expected_config.clone(), + })?, + } + .into()])?; + + assert_eq!(expected_config, gov.config()?); + + assert_eq!(gov.dao_uri()?.dao_uri, expected_config.dao_uri); + Ok(()) +} + +fn test_swap_governance(swaps: Vec<(u32, u32)>) { + let mock = MockBech32::new("mock"); + let gov_mod = DaoProposalSudo::new("proposal", mock.clone()); + let gov = DaoDaoCore::new("dao-core", mock.clone()); + gov_mod.upload().unwrap(); + let propmod_id = gov_mod.code_id().unwrap(); + gov.upload().unwrap(); + + let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { + root: mock.sender().to_string(), + }; + + let gov_instantiate = InstantiateMsg { + dao_uri: None, + admin: None, + name: "DAO DAO".to_string(), + description: "A DAO that builds DAOs.".to_string(), + image_url: None, + automatically_add_cw20s: true, + automatically_add_cw721s: true, + voting_module_instantiate_info: ModuleInstantiateInfo { + code_id: propmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "voting module".to_string(), + }, + proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { + code_id: propmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "governance module".to_string(), + }], + initial_items: None, + }; + + gov.instantiate(&gov_instantiate, None, None).unwrap(); + + let modules = gov.proposal_modules(None, None).unwrap(); + assert_eq!(modules.len(), 1); + let module_count = gov.proposal_module_count().unwrap(); + + assert_eq!( + module_count, + ProposalModuleCountResponse { + active_proposal_module_count: 1, + total_proposal_module_count: 1, + } + ); + + let (to_add, to_remove) = swaps + .iter() + .cloned() + .reduce(|(to_add, to_remove), (add, remove)| (to_add + add, to_remove + remove)) + .unwrap_or((0, 0)); + + for (add, remove) in swaps { + let start_modules = gov.proposal_modules(None, None).unwrap(); + + let start_modules_active: Vec = get_active_modules(&gov); + gov_mod.set_address(&start_modules_active[0].address.clone()); + let to_add: Vec<_> = (0..add) + .map(|n| ModuleInstantiateInfo { + code_id: propmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: format!("governance module {n}"), + }) + .collect(); + + let to_disable: Vec<_> = start_modules_active + .iter() + .rev() + .take(remove as usize) + .map(|a| a.address.to_string()) + .collect(); + + gov_mod + .proposal_execute(vec![WasmMsg::Execute { + contract_addr: gov.address().unwrap().to_string(), + funds: vec![], + msg: to_json_binary(&ExecuteMsg::UpdateProposalModules { to_add, to_disable }) + .unwrap(), + } + .into()]) + .unwrap(); + + let finish_modules_active = get_active_modules(&gov); + + assert_eq!( + finish_modules_active.len() as u32, + start_modules_active.len() as u32 + add - remove + ); + for module in start_modules + .clone() + .into_iter() + .rev() + .take(remove as usize) + { + assert!(!finish_modules_active.contains(&module)) + } + + let state: DumpStateResponse = gov.dump_state().unwrap(); + assert_eq!( + state.active_proposal_module_count, + finish_modules_active.len() as u32 + ); + + assert_eq!( + state.total_proposal_module_count, + start_modules.len() as u32 + add + ) + } + + let module_count = gov.proposal_module_count().unwrap(); + assert_eq!( + module_count, + ProposalModuleCountResponse { + active_proposal_module_count: 1 + to_add - to_remove, + total_proposal_module_count: 1 + to_add, + } + ); +} + +#[test] +fn test_update_governance() { + test_swap_governance(vec![(1, 1), (5, 0), (0, 5), (0, 0)]); + test_swap_governance(vec![(1, 1), (1, 1), (1, 1), (1, 1)]) +} + +#[test] +fn test_add_then_remove_governance() { + test_swap_governance(vec![(1, 0), (0, 1)]) +} + +#[test] +#[should_panic(expected = "Execution would result in no proposal modules being active.")] +fn test_swap_governance_bad() { + test_swap_governance(vec![(1, 1), (0, 1)]) +} + +#[test] +fn test_removed_modules_can_not_execute() { + let mock = MockBech32::new("mock"); + let gov_mod = DaoProposalSudo::new("proposal", mock.clone()); + let gov = DaoDaoCore::new("dao-core", mock.clone()); + gov_mod.upload().unwrap(); + let govmod_id = gov_mod.code_id().unwrap(); + gov.upload().unwrap(); + + let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { + root: mock.sender().to_string(), + }; + + let gov_instantiate = InstantiateMsg { + dao_uri: None, + admin: None, + name: "DAO DAO".to_string(), + description: "A DAO that builds DAOs.".to_string(), + image_url: None, + automatically_add_cw20s: true, + automatically_add_cw721s: true, + voting_module_instantiate_info: ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "voting module".to_string(), + }, + proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "governance module".to_string(), + }], + initial_items: None, + }; + + gov.instantiate(&gov_instantiate, None, None).unwrap(); + + let modules = gov.proposal_modules(None, None).unwrap(); + + assert_eq!(modules.len(), 1); + + let start_module = modules.into_iter().next().unwrap(); + gov_mod.set_address(&start_module.address); + + let to_add = vec![ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "new governance module".to_string(), + }]; + + let to_disable = vec![start_module.address.to_string()]; + + // Swap ourselves out. + gov_mod + .proposal_execute(vec![WasmMsg::Execute { + contract_addr: gov.address().unwrap().to_string(), + funds: vec![], + msg: to_json_binary(&ExecuteMsg::UpdateProposalModules { to_add, to_disable }).unwrap(), + } + .into()]) + .unwrap(); + + let finish_modules_active = get_active_modules(&gov); + + let new_proposal_module = finish_modules_active.into_iter().next().unwrap(); + + // Try to add a new module and remove the one we added + // earlier. This should fail as we have been removed. + let to_add = vec![ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "new governance module".to_string(), + }]; + let to_disable = vec![new_proposal_module.address.to_string()]; + + let err = gov_mod + .proposal_execute(vec![WasmMsg::Execute { + contract_addr: gov.address().unwrap().to_string(), + funds: vec![], + msg: to_json_binary(&ExecuteMsg::UpdateProposalModules { + to_add: to_add.clone(), + to_disable: to_disable.clone(), + }) + .unwrap(), + } + .into()]) + .unwrap_err(); + + println!("{}", err); + assert!(format!("{:?}", err) + .contains("Proposal module with address is disabled and cannot execute messages")); + + // Check that the enabled query works. + let enabled_modules = gov.active_proposal_modules(None, None).unwrap(); + + assert_eq!(enabled_modules, vec![new_proposal_module.clone()]); + + // The new proposal module should be able to perform actions. + gov_mod.set_address(&new_proposal_module.address); + gov_mod + .proposal_execute(vec![WasmMsg::Execute { + contract_addr: gov.address().unwrap().to_string(), + funds: vec![], + msg: to_json_binary(&ExecuteMsg::UpdateProposalModules { to_add, to_disable }).unwrap(), + } + .into()]) + .unwrap(); +} + +#[test] +fn test_module_already_disabled() { + let mut app = App::default(); + let govmod_id = app.store_code(sudo_proposal_contract()); + let gov_id = app.store_code(cw_core_contract()); + + let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { + root: CREATOR_ADDR.to_string(), + }; + + let gov_instantiate = InstantiateMsg { + dao_uri: None, + admin: None, + name: "DAO DAO".to_string(), + description: "A DAO that builds DAOs.".to_string(), + image_url: None, + automatically_add_cw20s: true, + automatically_add_cw721s: true, + voting_module_instantiate_info: ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "voting module".to_string(), + }, + proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "governance module".to_string(), + }], + initial_items: None, + }; + + let gov_addr = app + .instantiate_contract( + gov_id, + Addr::unchecked(CREATOR_ADDR), + &gov_instantiate, + &[], + "cw-governance", + None, + ) + .unwrap(); + + let modules: Vec = app + .wrap() + .query_wasm_smart( + gov_addr.clone(), + &QueryMsg::ProposalModules { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + assert_eq!(modules.len(), 1); + + let start_module = modules.into_iter().next().unwrap(); + + let to_disable = vec![ + start_module.address.to_string(), + start_module.address.to_string(), + ]; + + let err: ContractError = app + .execute_contract( + Addr::unchecked(CREATOR_ADDR), + start_module.address.clone(), + &dao_proposal_sudo::msg::ExecuteMsg::Execute { + msgs: vec![WasmMsg::Execute { + contract_addr: gov_addr.to_string(), + funds: vec![], + msg: to_json_binary(&ExecuteMsg::UpdateProposalModules { + to_add: vec![ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "governance module".to_string(), + }], + to_disable, + }) + .unwrap(), + } + .into()], + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!( + err, + ContractError::ModuleAlreadyDisabled { + address: start_module.address + } + ) +} + +#[test] +fn test_swap_voting_module() { + let mut app = App::default(); + let govmod_id = app.store_code(sudo_proposal_contract()); + let gov_id = app.store_code(cw_core_contract()); + + let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { + root: CREATOR_ADDR.to_string(), + }; + + let gov_instantiate = InstantiateMsg { + dao_uri: None, + admin: None, + name: "DAO DAO".to_string(), + description: "A DAO that builds DAOs.".to_string(), + image_url: None, + automatically_add_cw20s: true, + automatically_add_cw721s: true, + voting_module_instantiate_info: ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "voting module".to_string(), + }, + proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "governance module".to_string(), + }], + initial_items: None, + }; + + let gov_addr = app + .instantiate_contract( + gov_id, + Addr::unchecked(CREATOR_ADDR), + &gov_instantiate, + &[], + "cw-governance", + None, + ) + .unwrap(); + + let voting_addr: Addr = app + .wrap() + .query_wasm_smart(gov_addr.clone(), &QueryMsg::VotingModule {}) + .unwrap(); + + let modules: Vec = app + .wrap() + .query_wasm_smart( + gov_addr.clone(), + &QueryMsg::ProposalModules { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + assert_eq!(modules.len(), 1); + + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + modules[0].address.clone(), + &dao_proposal_sudo::msg::ExecuteMsg::Execute { + msgs: vec![WasmMsg::Execute { + contract_addr: gov_addr.to_string(), + funds: vec![], + msg: to_json_binary(&ExecuteMsg::UpdateVotingModule { + module: ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "voting module".to_string(), + }, + }) + .unwrap(), + } + .into()], + }, + &[], + ) + .unwrap(); + + let new_voting_addr: Addr = app + .wrap() + .query_wasm_smart(gov_addr, &QueryMsg::VotingModule {}) + .unwrap(); + + assert_ne!(new_voting_addr, voting_addr); +} + +fn test_unauthorized(app: &mut App, gov_addr: Addr, msg: ExecuteMsg) { + let err: ContractError = app + .execute_contract(Addr::unchecked(CREATOR_ADDR), gov_addr, &msg, &[]) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!(err, ContractError::Unauthorized {}); +} + +#[test] +fn test_permissions() { + let mut app = App::default(); + let govmod_id = app.store_code(sudo_proposal_contract()); + let gov_id = app.store_code(cw_core_contract()); + + let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { + root: CREATOR_ADDR.to_string(), + }; + + let gov_instantiate = InstantiateMsg { + dao_uri: None, + admin: None, + name: "DAO DAO".to_string(), + description: "A DAO that builds DAOs.".to_string(), + image_url: None, + voting_module_instantiate_info: ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "voting module".to_string(), + }, + proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "governance module".to_string(), + }], + initial_items: None, + automatically_add_cw20s: true, + automatically_add_cw721s: true, + }; + + let gov_addr = app + .instantiate_contract( + gov_id, + Addr::unchecked(CREATOR_ADDR), + &gov_instantiate, + &[], + "cw-governance", + None, + ) + .unwrap(); + + test_unauthorized( + &mut app, + gov_addr.clone(), + ExecuteMsg::UpdateVotingModule { + module: ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "voting module".to_string(), + }, + }, + ); + + test_unauthorized( + &mut app, + gov_addr.clone(), + ExecuteMsg::UpdateProposalModules { + to_add: vec![], + to_disable: vec![], + }, + ); + + test_unauthorized( + &mut app, + gov_addr, + ExecuteMsg::UpdateConfig { + config: Config { + dao_uri: None, + name: "Evil config.".to_string(), + description: "👿".to_string(), + image_url: None, + automatically_add_cw20s: true, + automatically_add_cw721s: true, + }, + }, + ); +} + +fn do_standard_instantiate(auto_add: bool, admin: Option) -> (Addr, App) { + let mut app = App::default(); + let govmod_id = app.store_code(sudo_proposal_contract()); + let voting_id = app.store_code(cw20_balances_voting()); + let gov_id = app.store_code(cw_core_contract()); + let cw20_id = app.store_code(cw20_contract()); + + let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { + root: CREATOR_ADDR.to_string(), + }; + let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { + token_info: dao_voting_cw20_balance::msg::TokenInfo::New { + code_id: cw20_id, + label: "DAO DAO voting".to_string(), + name: "DAO DAO".to_string(), + symbol: "DAO".to_string(), + decimals: 6, + initial_balances: vec![cw20::Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::from(2u64), + }], + marketing: None, + }, + }; + + let gov_instantiate = InstantiateMsg { + dao_uri: None, + admin, + name: "DAO DAO".to_string(), + description: "A DAO that builds DAOs.".to_string(), + image_url: None, + automatically_add_cw20s: auto_add, + automatically_add_cw721s: auto_add, + voting_module_instantiate_info: ModuleInstantiateInfo { + code_id: voting_id, + msg: to_json_binary(&voting_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "voting module".to_string(), + }, + proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "governance module".to_string(), + }], + initial_items: None, + }; + + let gov_addr = app + .instantiate_contract( + gov_id, + Addr::unchecked(CREATOR_ADDR), + &gov_instantiate, + &[], + "cw-governance", + None, + ) + .unwrap(); + + (gov_addr, app) +} + +#[test] +fn test_admin_permissions() { + let (core_addr, mut app) = do_standard_instantiate(true, None); + + let start_height = app.block_info().height; + let proposal_modules: Vec = app + .wrap() + .query_wasm_smart( + core_addr.clone(), + &QueryMsg::ProposalModules { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + assert_eq!(proposal_modules.len(), 1); + let proposal_module = proposal_modules.into_iter().next().unwrap(); + + // Random address can't call ExecuteAdminMsgs + let res = app.execute_contract( + Addr::unchecked("random"), + core_addr.clone(), + &ExecuteMsg::ExecuteAdminMsgs { + msgs: vec![WasmMsg::Execute { + contract_addr: core_addr.to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()], + }, + &[], + ); + res.unwrap_err(); + + // Proposal module can't call ExecuteAdminMsgs + let res = app.execute_contract( + proposal_module.address.clone(), + core_addr.clone(), + &ExecuteMsg::ExecuteAdminMsgs { + msgs: vec![WasmMsg::Execute { + contract_addr: core_addr.to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()], + }, + &[], + ); + res.unwrap_err(); + + // Update Admin can't be called by non-admins + let res = app.execute_contract( + Addr::unchecked("rando"), + core_addr.clone(), + &ExecuteMsg::NominateAdmin { + admin: Some("rando".to_string()), + }, + &[], + ); + res.unwrap_err(); + + // Nominate admin can be called by core contract as no admin was + // specified so the admin defaulted to the core contract. + let res = app.execute_contract( + proposal_module.address.clone(), + core_addr.clone(), + &ExecuteMsg::ExecuteProposalHook { + msgs: vec![WasmMsg::Execute { + contract_addr: core_addr.to_string(), + msg: to_json_binary(&ExecuteMsg::NominateAdmin { + admin: Some("meow".to_string()), + }) + .unwrap(), + funds: vec![], + } + .into()], + }, + &[], + ); + res.unwrap(); + + // Instantiate new DAO with an admin + let (core_with_admin_addr, mut app) = + do_standard_instantiate(true, Some(Addr::unchecked("admin").to_string())); + + // Non admins still can't call ExecuteAdminMsgs + let res = app.execute_contract( + proposal_module.address, + core_with_admin_addr.clone(), + &ExecuteMsg::ExecuteAdminMsgs { + msgs: vec![WasmMsg::Execute { + contract_addr: core_with_admin_addr.to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()], + }, + &[], + ); + res.unwrap_err(); + + // Admin cannot directly pause the DAO + let res = app.execute_contract( + Addr::unchecked("admin"), + core_with_admin_addr.clone(), + &ExecuteMsg::Pause { + duration: Duration::Height(10), + }, + &[], + ); + assert!(res.is_err()); + + // Random person cannot pause the DAO + let res = app.execute_contract( + Addr::unchecked("random"), + core_with_admin_addr.clone(), + &ExecuteMsg::Pause { + duration: Duration::Height(10), + }, + &[], + ); + assert!(res.is_err()); + + // Admin can call ExecuteAdminMsgs, here an admin pauses the DAO + let res = app.execute_contract( + Addr::unchecked("admin"), + core_with_admin_addr.clone(), + &ExecuteMsg::ExecuteAdminMsgs { + msgs: vec![WasmMsg::Execute { + contract_addr: core_with_admin_addr.to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()], + }, + &[], + ); + assert!(res.is_ok()); + + // Ensure we are paused for 10 blocks + let paused: PauseInfoResponse = app + .wrap() + .query_wasm_smart(core_with_admin_addr.clone(), &QueryMsg::PauseInfo {}) + .unwrap(); + assert_eq!( + paused, + PauseInfoResponse::Paused { + expiration: Expiration::AtHeight(start_height + 10) + } + ); + + // DAO unpauses after 10 blocks + app.update_block(|block| block.height += 11); + + // Check we are unpaused + let paused: PauseInfoResponse = app + .wrap() + .query_wasm_smart(core_with_admin_addr.clone(), &QueryMsg::PauseInfo {}) + .unwrap(); + assert_eq!(paused, PauseInfoResponse::Unpaused {}); + + // Admin pauses DAO again + let res = app.execute_contract( + Addr::unchecked("admin"), + core_with_admin_addr.clone(), + &ExecuteMsg::ExecuteAdminMsgs { + msgs: vec![WasmMsg::Execute { + contract_addr: core_with_admin_addr.to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()], + }, + &[], + ); + assert!(res.is_ok()); + + // DAO with admin cannot unpause itself + let res = app.execute_contract( + core_with_admin_addr.clone(), + core_with_admin_addr.clone(), + &ExecuteMsg::Unpause {}, + &[], + ); + assert!(res.is_err()); + + // Random person cannot unpause the DAO + let res = app.execute_contract( + Addr::unchecked("random"), + core_with_admin_addr.clone(), + &ExecuteMsg::Unpause {}, + &[], + ); + assert!(res.is_err()); + + // Admin can unpause the DAO directly + let res = app.execute_contract( + Addr::unchecked("admin"), + core_with_admin_addr.clone(), + &ExecuteMsg::Unpause {}, + &[], + ); + assert!(res.is_ok()); + + // Check we are unpaused + let paused: PauseInfoResponse = app + .wrap() + .query_wasm_smart(core_with_admin_addr.clone(), &QueryMsg::PauseInfo {}) + .unwrap(); + assert_eq!(paused, PauseInfoResponse::Unpaused {}); + + // Admin can nominate a new admin. + let res = app.execute_contract( + Addr::unchecked("admin"), + core_with_admin_addr.clone(), + &ExecuteMsg::NominateAdmin { + admin: Some("meow".to_string()), + }, + &[], + ); + res.unwrap(); + + let nomination: AdminNominationResponse = app + .wrap() + .query_wasm_smart(core_with_admin_addr.clone(), &QueryMsg::AdminNomination {}) + .unwrap(); + assert_eq!( + nomination, + AdminNominationResponse { + nomination: Some(Addr::unchecked("meow")) + } + ); + + // Check that admin has not yet been updated + let res: Addr = app + .wrap() + .query_wasm_smart(core_with_admin_addr.clone(), &QueryMsg::Admin {}) + .unwrap(); + assert_eq!(res, Addr::unchecked("admin")); + + // Only the nominated address may accept the nomination. + let err: ContractError = app + .execute_contract( + Addr::unchecked("random"), + core_with_admin_addr.clone(), + &ExecuteMsg::AcceptAdminNomination {}, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, ContractError::Unauthorized {}); + + // Accept the nomination. + app.execute_contract( + Addr::unchecked("meow"), + core_with_admin_addr.clone(), + &ExecuteMsg::AcceptAdminNomination {}, + &[], + ) + .unwrap(); + + // Check that admin has been updated + let res: Addr = app + .wrap() + .query_wasm_smart(core_with_admin_addr.clone(), &QueryMsg::Admin {}) + .unwrap(); + assert_eq!(res, Addr::unchecked("meow")); + + // Check that the pending admin has been cleared. + let nomination: AdminNominationResponse = app + .wrap() + .query_wasm_smart(core_with_admin_addr, &QueryMsg::AdminNomination {}) + .unwrap(); + assert_eq!(nomination, AdminNominationResponse { nomination: None }); +} + +#[test] +fn test_admin_nomination() { + let (core_addr, mut app) = do_standard_instantiate(true, Some("admin".to_string())); + + // Check that there is no pending nominations. + let nomination: AdminNominationResponse = app + .wrap() + .query_wasm_smart(core_addr.clone(), &QueryMsg::AdminNomination {}) + .unwrap(); + assert_eq!(nomination, AdminNominationResponse { nomination: None }); + + // Nominate a new admin. + app.execute_contract( + Addr::unchecked("admin"), + core_addr.clone(), + &ExecuteMsg::NominateAdmin { + admin: Some("ekez".to_string()), + }, + &[], + ) + .unwrap(); + + // Check that the nomination is in place. + let nomination: AdminNominationResponse = app + .wrap() + .query_wasm_smart(core_addr.clone(), &QueryMsg::AdminNomination {}) + .unwrap(); + assert_eq!( + nomination, + AdminNominationResponse { + nomination: Some(Addr::unchecked("ekez")) + } + ); + + // Non-admin can not withdraw. + let err: ContractError = app + .execute_contract( + Addr::unchecked("ekez"), + core_addr.clone(), + &ExecuteMsg::WithdrawAdminNomination {}, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, ContractError::Unauthorized {}); + + // Admin can withdraw. + app.execute_contract( + Addr::unchecked("admin"), + core_addr.clone(), + &ExecuteMsg::WithdrawAdminNomination {}, + &[], + ) + .unwrap(); + + // Check that the nomination is withdrawn. + let nomination: AdminNominationResponse = app + .wrap() + .query_wasm_smart(core_addr.clone(), &QueryMsg::AdminNomination {}) + .unwrap(); + assert_eq!(nomination, AdminNominationResponse { nomination: None }); + + // Can not withdraw if no nomination is pending. + let err: ContractError = app + .execute_contract( + Addr::unchecked("admin"), + core_addr.clone(), + &ExecuteMsg::WithdrawAdminNomination {}, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, ContractError::NoAdminNomination {}); + + // Can not claim nomination b/c it has been withdrawn. + let err: ContractError = app + .execute_contract( + Addr::unchecked("ekez"), + core_addr.clone(), + &ExecuteMsg::AcceptAdminNomination {}, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, ContractError::NoAdminNomination {}); + + // Nominate a new admin. + app.execute_contract( + Addr::unchecked("admin"), + core_addr.clone(), + &ExecuteMsg::NominateAdmin { + admin: Some("meow".to_string()), + }, + &[], + ) + .unwrap(); + + // A new nomination can not be created if there is already a + // pending nomination. + let err: ContractError = app + .execute_contract( + Addr::unchecked("admin"), + core_addr.clone(), + &ExecuteMsg::NominateAdmin { + admin: Some("arthur".to_string()), + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, ContractError::PendingNomination {}); + + // Only nominated admin may accept. + let err: ContractError = app + .execute_contract( + Addr::unchecked("ekez"), + core_addr.clone(), + &ExecuteMsg::AcceptAdminNomination {}, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, ContractError::Unauthorized {}); + + app.execute_contract( + Addr::unchecked("meow"), + core_addr.clone(), + &ExecuteMsg::AcceptAdminNomination {}, + &[], + ) + .unwrap(); + + // Check that meow is the new admin. + let admin: Addr = app + .wrap() + .query_wasm_smart(core_addr.clone(), &QueryMsg::Admin {}) + .unwrap(); + assert_eq!(admin, Addr::unchecked("meow".to_string())); + + let start_height = app.block_info().height; + // Check that the new admin can do admin things and the old can not. + let err: ContractError = app + .execute_contract( + Addr::unchecked("admin"), + core_addr.clone(), + &ExecuteMsg::ExecuteAdminMsgs { + msgs: vec![WasmMsg::Execute { + contract_addr: core_addr.to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()], + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, ContractError::Unauthorized {}); + + let res = app.execute_contract( + Addr::unchecked("meow"), + core_addr.clone(), + &ExecuteMsg::ExecuteAdminMsgs { + msgs: vec![WasmMsg::Execute { + contract_addr: core_addr.to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()], + }, + &[], + ); + res.unwrap(); + + let paused: PauseInfoResponse = app + .wrap() + .query_wasm_smart(core_addr.clone(), &QueryMsg::PauseInfo {}) + .unwrap(); + assert_eq!( + paused, + PauseInfoResponse::Paused { + expiration: Expiration::AtHeight(start_height + 10) + } + ); + + // DAO unpauses after 10 blocks + app.update_block(|block| block.height += 11); + + // Remove the admin. + app.execute_contract( + Addr::unchecked("meow"), + core_addr.clone(), + &ExecuteMsg::NominateAdmin { admin: None }, + &[], + ) + .unwrap(); + + // Check that this has not caused an admin to be nominated. + let nomination: AdminNominationResponse = app + .wrap() + .query_wasm_smart(core_addr.clone(), &QueryMsg::AdminNomination {}) + .unwrap(); + assert_eq!(nomination, AdminNominationResponse { nomination: None }); + + // Check that admin has been updated. As there was no admin + // nominated the admin should revert back to the contract address. + let res: Addr = app + .wrap() + .query_wasm_smart(core_addr.clone(), &QueryMsg::Admin {}) + .unwrap(); + assert_eq!(res, core_addr); +} + +#[test] +fn test_passthrough_voting_queries() { + let (gov_addr, app) = do_standard_instantiate(true, None); + + let creator_voting_power: VotingPowerAtHeightResponse = app + .wrap() + .query_wasm_smart( + gov_addr, + &QueryMsg::VotingPowerAtHeight { + address: CREATOR_ADDR.to_string(), + height: None, + }, + ) + .unwrap(); + + assert_eq!( + creator_voting_power, + VotingPowerAtHeightResponse { + power: Uint128::from(2u64), + height: app.block_info().height, + } + ); +} + +fn set_item(app: &mut App, gov_addr: Addr, key: String, value: String) { + app.execute_contract( + gov_addr.clone(), + gov_addr, + &ExecuteMsg::SetItem { key, value }, + &[], + ) + .unwrap(); +} + +fn remove_item(app: &mut App, gov_addr: Addr, key: String) { + app.execute_contract( + gov_addr.clone(), + gov_addr, + &ExecuteMsg::RemoveItem { key }, + &[], + ) + .unwrap(); +} + +fn get_item(app: &mut App, gov_addr: Addr, key: String) -> GetItemResponse { + app.wrap() + .query_wasm_smart(gov_addr, &QueryMsg::GetItem { key }) + .unwrap() +} + +fn list_items( + app: &mut App, + gov_addr: Addr, + start_at: Option, + limit: Option, +) -> Vec<(String, String)> { + app.wrap() + .query_wasm_smart( + gov_addr, + &QueryMsg::ListItems { + start_after: start_at, + limit, + }, + ) + .unwrap() +} + +#[test] +fn test_item_permissions() { + let (gov_addr, mut app) = do_standard_instantiate(true, None); + + let err: ContractError = app + .execute_contract( + Addr::unchecked("ekez"), + gov_addr.clone(), + &ExecuteMsg::SetItem { + key: "k".to_string(), + value: "v".to_string(), + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, ContractError::Unauthorized {}); + + let err: ContractError = app + .execute_contract( + Addr::unchecked("ekez"), + gov_addr, + &ExecuteMsg::RemoveItem { + key: "k".to_string(), + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, ContractError::Unauthorized {}); +} + +#[test] +fn test_add_remove_get() { + let (gov_addr, mut app) = do_standard_instantiate(true, None); + + let a = get_item(&mut app, gov_addr.clone(), "aaaaa".to_string()); + assert_eq!(a, GetItemResponse { item: None }); + + set_item( + &mut app, + gov_addr.clone(), + "aaaaakey".to_string(), + "aaaaaaddr".to_string(), + ); + let a = get_item(&mut app, gov_addr.clone(), "aaaaakey".to_string()); + assert_eq!( + a, + GetItemResponse { + item: Some("aaaaaaddr".to_string()) + } + ); + + remove_item(&mut app, gov_addr.clone(), "aaaaakey".to_string()); + let a = get_item(&mut app, gov_addr, "aaaaakey".to_string()); + assert_eq!(a, GetItemResponse { item: None }); +} + +#[test] +#[should_panic(expected = "Key is missing from storage")] +fn test_remove_missing_key() { + let (gov_addr, mut app) = do_standard_instantiate(true, None); + remove_item(&mut app, gov_addr, "b".to_string()) +} + +#[test] +fn test_list_items() { + let mut app = App::default(); + let govmod_id = app.store_code(sudo_proposal_contract()); + let voting_id = app.store_code(cw20_balances_voting()); + let gov_id = app.store_code(cw_core_contract()); + let cw20_id = app.store_code(cw20_contract()); + + let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { + root: CREATOR_ADDR.to_string(), + }; + let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { + token_info: dao_voting_cw20_balance::msg::TokenInfo::New { + code_id: cw20_id, + label: "DAO DAO voting".to_string(), + name: "DAO DAO".to_string(), + symbol: "DAO".to_string(), + decimals: 6, + initial_balances: vec![cw20::Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::from(2u64), + }], + marketing: None, + }, + }; + + let gov_instantiate = InstantiateMsg { + dao_uri: None, + admin: None, + name: "DAO DAO".to_string(), + description: "A DAO that builds DAOs.".to_string(), + image_url: None, + automatically_add_cw20s: true, + automatically_add_cw721s: true, + voting_module_instantiate_info: ModuleInstantiateInfo { + code_id: voting_id, + msg: to_json_binary(&voting_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "voting module".to_string(), + }, + proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "governance module".to_string(), + }], + initial_items: None, + }; + + let gov_addr = app + .instantiate_contract( + gov_id, + Addr::unchecked(CREATOR_ADDR), + &gov_instantiate, + &[], + "cw-governance", + None, + ) + .unwrap(); + + set_item( + &mut app, + gov_addr.clone(), + "fookey".to_string(), + "fooaddr".to_string(), + ); + set_item( + &mut app, + gov_addr.clone(), + "barkey".to_string(), + "baraddr".to_string(), + ); + set_item( + &mut app, + gov_addr.clone(), + "loremkey".to_string(), + "loremaddr".to_string(), + ); + set_item( + &mut app, + gov_addr.clone(), + "ipsumkey".to_string(), + "ipsumaddr".to_string(), + ); + + // Foo returned as we are only getting one item and items are in + // decending order. + let first_item = list_items(&mut app, gov_addr.clone(), None, Some(1)); + assert_eq!(first_item.len(), 1); + assert_eq!( + first_item[0], + ("loremkey".to_string(), "loremaddr".to_string()) + ); + + let no_items = list_items(&mut app, gov_addr.clone(), None, Some(0)); + assert_eq!(no_items.len(), 0); + + // Items are retreived in decending order so asking for foo with + // no limit ought to give us the barkey k/v. this will be the last item + // note: the paginate map bound is exclusive, so fookey will be starting point + let last_item = list_items(&mut app, gov_addr.clone(), Some("foo".to_string()), None); + assert_eq!(last_item.len(), 1); + assert_eq!(last_item[0], ("barkey".to_string(), "baraddr".to_string())); + + // Items are retreived in decending order so asking for ipsum with + // 4 limit ought to give us the fookey and barkey k/vs. + let after_foo_list = list_items(&mut app, gov_addr, Some("ipsum".to_string()), Some(4)); + assert_eq!(after_foo_list.len(), 2); + assert_eq!( + after_foo_list, + vec![ + ("fookey".to_string(), "fooaddr".to_string()), + ("barkey".to_string(), "baraddr".to_string()) + ] + ); +} + +#[test] +fn test_instantiate_with_items() { + let mut app = App::default(); + let govmod_id = app.store_code(sudo_proposal_contract()); + let voting_id = app.store_code(cw20_balances_voting()); + let gov_id = app.store_code(cw_core_contract()); + let cw20_id = app.store_code(cw20_contract()); + + let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { + root: CREATOR_ADDR.to_string(), + }; + let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { + token_info: dao_voting_cw20_balance::msg::TokenInfo::New { + code_id: cw20_id, + label: "DAO DAO voting".to_string(), + name: "DAO DAO".to_string(), + symbol: "DAO".to_string(), + decimals: 6, + initial_balances: vec![cw20::Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::from(2u64), + }], + marketing: None, + }, + }; + + let mut initial_items = vec![ + InitialItem { + key: "item0".to_string(), + value: "item0_value".to_string(), + }, + InitialItem { + key: "item1".to_string(), + value: "item1_value".to_string(), + }, + InitialItem { + key: "item0".to_string(), + value: "item0_value_override".to_string(), + }, + ]; + + let mut gov_instantiate = InstantiateMsg { + dao_uri: None, + admin: None, + name: "DAO DAO".to_string(), + description: "A DAO that builds DAOs.".to_string(), + image_url: None, + automatically_add_cw20s: true, + automatically_add_cw721s: true, + voting_module_instantiate_info: ModuleInstantiateInfo { + code_id: voting_id, + msg: to_json_binary(&voting_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "voting module".to_string(), + }, + proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "governance module".to_string(), + }], + initial_items: Some(initial_items.clone()), + }; + + // Ensure duplicates are dissallowed. + let err: ContractError = app + .instantiate_contract( + gov_id, + Addr::unchecked(CREATOR_ADDR), + &gov_instantiate, + &[], + "cw-governance", + None, + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!( + err, + ContractError::DuplicateInitialItem { + item: "item0".to_string() + } + ); + + initial_items.pop(); + gov_instantiate.initial_items = Some(initial_items); + let gov_addr = app + .instantiate_contract( + gov_id, + Addr::unchecked(CREATOR_ADDR), + &gov_instantiate, + &[], + "cw-governance", + None, + ) + .unwrap(); + + // Ensure initial items were added. + let items = list_items(&mut app, gov_addr.clone(), None, None); + assert_eq!(items.len(), 2); + + // Descending order, so item1 is first. + assert_eq!(items[1].0, "item0".to_string()); + let get_item0 = get_item(&mut app, gov_addr.clone(), "item0".to_string()); + assert_eq!( + get_item0, + GetItemResponse { + item: Some("item0_value".to_string()), + } + ); + + assert_eq!(items[0].0, "item1".to_string()); + let item1_value = get_item(&mut app, gov_addr, "item1".to_string()).item; + assert_eq!(item1_value, Some("item1_value".to_string())) +} + +#[test] +fn test_cw20_receive_auto_add() { + let (gov_addr, mut app) = do_standard_instantiate(true, None); + + let cw20_id = app.store_code(cw20_contract()); + let another_cw20 = app + .instantiate_contract( + cw20_id, + Addr::unchecked(CREATOR_ADDR), + &cw20_base::msg::InstantiateMsg { + name: "DAO".to_string(), + symbol: "DAO".to_string(), + decimals: 6, + initial_balances: vec![], + mint: None, + marketing: None, + }, + &[], + "another-token", + None, + ) + .unwrap(); + + let voting_module: Addr = app + .wrap() + .query_wasm_smart(gov_addr.clone(), &QueryMsg::VotingModule {}) + .unwrap(); + let gov_token: Addr = app + .wrap() + .query_wasm_smart( + voting_module, + &dao_interface::voting::Query::TokenContract {}, + ) + .unwrap(); + + // Check that the balances query works with no tokens. + let cw20_balances: Vec = app + .wrap() + .query_wasm_smart( + gov_addr.clone(), + &QueryMsg::Cw20Balances { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!(cw20_balances, vec![]); + + // Send a gov token to the governance contract. + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + gov_token.clone(), + &cw20::Cw20ExecuteMsg::Send { + contract: gov_addr.to_string(), + amount: Uint128::new(1), + msg: to_json_binary(&"").unwrap(), + }, + &[], + ) + .unwrap(); + + let cw20_list: Vec = app + .wrap() + .query_wasm_smart( + gov_addr.clone(), + &QueryMsg::Cw20TokenList { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!(cw20_list, vec![gov_token.clone()]); + + let cw20_balances: Vec = app + .wrap() + .query_wasm_smart( + gov_addr.clone(), + &QueryMsg::Cw20Balances { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!( + cw20_balances, + vec![Cw20BalanceResponse { + addr: gov_token.clone(), + balance: Uint128::new(1), + }] + ); + + // Test removing and adding some new ones. Invalid should fail. + let err: ContractError = app + .execute_contract( + Addr::unchecked(gov_addr.clone()), + gov_addr.clone(), + &ExecuteMsg::UpdateCw20List { + to_add: vec!["new".to_string()], + to_remove: vec![gov_token.to_string()], + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert!(matches!(err, ContractError::Std(_))); + + // Test that non-DAO can not update the list. + let err: ContractError = app + .execute_contract( + Addr::unchecked("ekez"), + gov_addr.clone(), + &ExecuteMsg::UpdateCw20List { + to_add: vec![], + to_remove: vec![gov_token.to_string()], + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert!(matches!(err, ContractError::Unauthorized {})); + + app.execute_contract( + Addr::unchecked(gov_addr.clone()), + gov_addr.clone(), + &ExecuteMsg::UpdateCw20List { + to_add: vec![another_cw20.to_string()], + to_remove: vec![gov_token.to_string()], + }, + &[], + ) + .unwrap(); + + let cw20_list: Vec = app + .wrap() + .query_wasm_smart( + gov_addr, + &QueryMsg::Cw20TokenList { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!(cw20_list, vec![another_cw20]); +} + +#[test] +fn test_cw20_receive_no_auto_add() { + let (gov_addr, mut app) = do_standard_instantiate(false, None); + + let cw20_id = app.store_code(cw20_contract()); + let another_cw20 = app + .instantiate_contract( + cw20_id, + Addr::unchecked(CREATOR_ADDR), + &cw20_base::msg::InstantiateMsg { + name: "DAO".to_string(), + symbol: "DAO".to_string(), + decimals: 6, + initial_balances: vec![], + mint: None, + marketing: None, + }, + &[], + "another-token", + None, + ) + .unwrap(); + + let voting_module: Addr = app + .wrap() + .query_wasm_smart(gov_addr.clone(), &QueryMsg::VotingModule {}) + .unwrap(); + let gov_token: Addr = app + .wrap() + .query_wasm_smart( + voting_module, + &dao_interface::voting::Query::TokenContract {}, + ) + .unwrap(); + + // Send a gov token to the governance contract. Should not be + // added becasue auto add is turned off. + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + gov_token.clone(), + &cw20::Cw20ExecuteMsg::Send { + contract: gov_addr.to_string(), + amount: Uint128::new(1), + msg: to_json_binary(&"").unwrap(), + }, + &[], + ) + .unwrap(); + + let cw20_list: Vec = app + .wrap() + .query_wasm_smart( + gov_addr.clone(), + &QueryMsg::Cw20TokenList { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!(cw20_list, Vec::::new()); + + app.execute_contract( + Addr::unchecked(gov_addr.clone()), + gov_addr.clone(), + &ExecuteMsg::UpdateCw20List { + to_add: vec![another_cw20.to_string(), gov_token.to_string()], + to_remove: vec!["ok to remove non existent".to_string()], + }, + &[], + ) + .unwrap(); + + let cw20_list: Vec = app + .wrap() + .query_wasm_smart( + gov_addr, + &QueryMsg::Cw20TokenList { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!(cw20_list, vec![another_cw20, gov_token]); +} + +#[test] +fn test_cw721_receive() { + let (gov_addr, mut app) = do_standard_instantiate(true, None); + + let cw721_id = app.store_code(cw721_contract()); + + let cw721_addr = app + .instantiate_contract( + cw721_id, + Addr::unchecked(CREATOR_ADDR), + &cw721_base::msg::InstantiateMsg { + name: "ekez".to_string(), + symbol: "ekez".to_string(), + minter: CREATOR_ADDR.to_string(), + }, + &[], + "cw721", + None, + ) + .unwrap(); + + let another_cw721 = app + .instantiate_contract( + cw721_id, + Addr::unchecked(CREATOR_ADDR), + &cw721_base::msg::InstantiateMsg { + name: "ekez".to_string(), + symbol: "ekez".to_string(), + minter: CREATOR_ADDR.to_string(), + }, + &[], + "cw721", + None, + ) + .unwrap(); + + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + cw721_addr.clone(), + &cw721_base::msg::ExecuteMsg::, Empty>::Mint { + token_id: "ekez".to_string(), + owner: CREATOR_ADDR.to_string(), + token_uri: None, + extension: None, + }, + &[], + ) + .unwrap(); + + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + cw721_addr.clone(), + &cw721_base::msg::ExecuteMsg::, Empty>::SendNft { + contract: gov_addr.to_string(), + token_id: "ekez".to_string(), + msg: to_json_binary("").unwrap(), + }, + &[], + ) + .unwrap(); + + let cw721_list: Vec = app + .wrap() + .query_wasm_smart( + gov_addr.clone(), + &QueryMsg::Cw721TokenList { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!(cw721_list, vec![cw721_addr.clone()]); + + // Try to add an invalid cw721. + let err: ContractError = app + .execute_contract( + Addr::unchecked(gov_addr.clone()), + gov_addr.clone(), + &ExecuteMsg::UpdateCw721List { + to_add: vec!["new".to_string(), cw721_addr.to_string()], + to_remove: vec![cw721_addr.to_string()], + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert!(matches!(err, ContractError::Std(_))); + + // Test that non-DAO can not update the list. + let err: ContractError = app + .execute_contract( + Addr::unchecked("ekez"), + gov_addr.clone(), + &ExecuteMsg::UpdateCw721List { + to_add: vec![], + to_remove: vec![cw721_addr.to_string()], + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert!(matches!(err, ContractError::Unauthorized {})); + + // Add a real cw721. + app.execute_contract( + Addr::unchecked(gov_addr.clone()), + gov_addr.clone(), + &ExecuteMsg::UpdateCw721List { + to_add: vec![another_cw721.to_string(), cw721_addr.to_string()], + to_remove: vec![cw721_addr.to_string()], + }, + &[], + ) + .unwrap(); + + let cw20_list: Vec = app + .wrap() + .query_wasm_smart( + gov_addr, + &QueryMsg::Cw721TokenList { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!(cw20_list, vec![another_cw721]); +} + +#[test] +fn test_cw721_receive_no_auto_add() { + let (gov_addr, mut app) = do_standard_instantiate(false, None); + + let cw721_id = app.store_code(cw721_contract()); + + let cw721_addr = app + .instantiate_contract( + cw721_id, + Addr::unchecked(CREATOR_ADDR), + &cw721_base::msg::InstantiateMsg { + name: "ekez".to_string(), + symbol: "ekez".to_string(), + minter: CREATOR_ADDR.to_string(), + }, + &[], + "cw721", + None, + ) + .unwrap(); + + let another_cw721 = app + .instantiate_contract( + cw721_id, + Addr::unchecked(CREATOR_ADDR), + &cw721_base::msg::InstantiateMsg { + name: "ekez".to_string(), + symbol: "ekez".to_string(), + minter: CREATOR_ADDR.to_string(), + }, + &[], + "cw721", + None, + ) + .unwrap(); + + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + cw721_addr.clone(), + &cw721_base::msg::ExecuteMsg::, Empty>::Mint { + token_id: "ekez".to_string(), + owner: CREATOR_ADDR.to_string(), + token_uri: None, + extension: None, + }, + &[], + ) + .unwrap(); + + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + cw721_addr.clone(), + &cw721_base::msg::ExecuteMsg::, Empty>::SendNft { + contract: gov_addr.to_string(), + token_id: "ekez".to_string(), + msg: to_json_binary("").unwrap(), + }, + &[], + ) + .unwrap(); + + let cw721_list: Vec = app + .wrap() + .query_wasm_smart( + gov_addr.clone(), + &QueryMsg::Cw721TokenList { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!(cw721_list, Vec::::new()); + + // Duplicates OK. Just adds one. + app.execute_contract( + Addr::unchecked(gov_addr.clone()), + gov_addr.clone(), + &ExecuteMsg::UpdateCw721List { + to_add: vec![ + another_cw721.to_string(), + cw721_addr.to_string(), + cw721_addr.to_string(), + ], + to_remove: vec![], + }, + &[], + ) + .unwrap(); + + let cw20_list: Vec = app + .wrap() + .query_wasm_smart( + gov_addr, + &QueryMsg::Cw721TokenList { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!(cw20_list, vec![another_cw721, cw721_addr]); +} + +#[test] +fn test_pause() { + let (core_addr, mut app) = do_standard_instantiate(false, None); + + let start_height = app.block_info().height; + + let proposal_modules: Vec = app + .wrap() + .query_wasm_smart( + core_addr.clone(), + &QueryMsg::ProposalModules { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + assert_eq!(proposal_modules.len(), 1); + let proposal_module = proposal_modules.into_iter().next().unwrap(); + + let paused: PauseInfoResponse = app + .wrap() + .query_wasm_smart(core_addr.clone(), &QueryMsg::PauseInfo {}) + .unwrap(); + assert_eq!(paused, PauseInfoResponse::Unpaused {}); + let all_state: DumpStateResponse = app + .wrap() + .query_wasm_smart(core_addr.clone(), &QueryMsg::DumpState {}) + .unwrap(); + assert_eq!(all_state.pause_info, PauseInfoResponse::Unpaused {}); + + // DAO is not paused. Check that we can execute things. + // + // Tests intentionally use the core address to send these + // messsages to simulate a worst case scenerio where the core + // contract has a vulnerability. + app.execute_contract( + core_addr.clone(), + core_addr.clone(), + &ExecuteMsg::UpdateConfig { + config: Config { + dao_uri: None, + name: "The Empire Strikes Back".to_string(), + description: "haha lol we have pwned your DAO".to_string(), + image_url: None, + automatically_add_cw20s: true, + automatically_add_cw721s: true, + }, + }, + &[], + ) + .unwrap(); + + // Oh no the DAO is under attack! Quick! Pause the DAO while we + // figure out what to do! + let err: ContractError = app + .execute_contract( + proposal_module.address.clone(), + core_addr.clone(), + &ExecuteMsg::Pause { + duration: Duration::Height(10), + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + + // Only the DAO may call this on itself. Proposal modules must use + // the execute hook. + assert_eq!(err, ContractError::Unauthorized {}); + + app.execute_contract( + proposal_module.address.clone(), + core_addr.clone(), + &ExecuteMsg::ExecuteProposalHook { + msgs: vec![WasmMsg::Execute { + contract_addr: core_addr.to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()], + }, + &[], + ) + .unwrap(); + + let paused: PauseInfoResponse = app + .wrap() + .query_wasm_smart(core_addr.clone(), &QueryMsg::PauseInfo {}) + .unwrap(); + assert_eq!( + paused, + PauseInfoResponse::Paused { + expiration: Expiration::AtHeight(start_height + 10) + } + ); + let all_state: DumpStateResponse = app + .wrap() + .query_wasm_smart(core_addr.clone(), &QueryMsg::DumpState {}) + .unwrap(); + assert_eq!( + all_state.pause_info, + PauseInfoResponse::Paused { + expiration: Expiration::AtHeight(start_height + 10) + } + ); + + // This should actually be allowed to enable the admin to execute + let result = app.execute_contract( + core_addr.clone(), + core_addr.clone(), + &ExecuteMsg::UpdateConfig { + config: Config { + dao_uri: None, + name: "The Empire Strikes Back Again".to_string(), + description: "haha lol we have pwned your DAO again".to_string(), + image_url: None, + automatically_add_cw20s: true, + automatically_add_cw721s: true, + }, + }, + &[], + ); + assert!(result.is_ok()); + + let err: ContractError = app + .execute_contract( + proposal_module.address.clone(), + core_addr.clone(), + &ExecuteMsg::ExecuteProposalHook { + msgs: vec![WasmMsg::Execute { + contract_addr: core_addr.to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()], + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + + assert!(matches!(err, ContractError::Paused { .. })); + + app.update_block(|block| block.height += 9); + + // Still not unpaused. + let err: ContractError = app + .execute_contract( + proposal_module.address.clone(), + core_addr.clone(), + &ExecuteMsg::ExecuteProposalHook { + msgs: vec![WasmMsg::Execute { + contract_addr: core_addr.to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()], + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + + assert!(matches!(err, ContractError::Paused { .. })); + + app.update_block(|block| block.height += 1); + + let paused: PauseInfoResponse = app + .wrap() + .query_wasm_smart(core_addr.clone(), &QueryMsg::PauseInfo {}) + .unwrap(); + assert_eq!(paused, PauseInfoResponse::Unpaused {}); + let all_state: DumpStateResponse = app + .wrap() + .query_wasm_smart(core_addr.clone(), &QueryMsg::DumpState {}) + .unwrap(); + assert_eq!(all_state.pause_info, PauseInfoResponse::Unpaused {}); + + // Now its unpaused so we should be able to pause again. + app.execute_contract( + proposal_module.address, + core_addr.clone(), + &ExecuteMsg::ExecuteProposalHook { + msgs: vec![WasmMsg::Execute { + contract_addr: core_addr.to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()], + }, + &[], + ) + .unwrap(); + + let paused: PauseInfoResponse = app + .wrap() + .query_wasm_smart(core_addr.clone(), &QueryMsg::PauseInfo {}) + .unwrap(); + assert_eq!( + paused, + PauseInfoResponse::Paused { + expiration: Expiration::AtHeight(start_height + 20) + } + ); + let all_state: DumpStateResponse = app + .wrap() + .query_wasm_smart(core_addr, &QueryMsg::DumpState {}) + .unwrap(); + assert_eq!( + all_state.pause_info, + PauseInfoResponse::Paused { + expiration: Expiration::AtHeight(start_height + 20) + } + ); +} + +#[test] +fn test_dump_state_proposal_modules() { + let (core_addr, app) = do_standard_instantiate(false, None); + let proposal_modules: Vec = app + .wrap() + .query_wasm_smart( + core_addr.clone(), + &QueryMsg::ProposalModules { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + assert_eq!(proposal_modules.len(), 1); + let proposal_module = proposal_modules.into_iter().next().unwrap(); + + let all_state: DumpStateResponse = app + .wrap() + .query_wasm_smart(core_addr, &QueryMsg::DumpState {}) + .unwrap(); + assert_eq!(all_state.pause_info, PauseInfoResponse::Unpaused {}); + assert_eq!(all_state.proposal_modules.len(), 1); + assert_eq!(all_state.proposal_modules[0], proposal_module); +} + +// Note that this isn't actually testing that we are migrating from the previous version since +// with multitest contract instantiation we can't manipulate storage to the previous version of state before invoking migrate. So if anything, +// this just tests the idempotency of migrate. +#[test] +fn test_migrate_from_compatible() { + let mut app = App::default(); + let govmod_id = app.store_code(sudo_proposal_contract()); + let voting_id = app.store_code(cw20_balances_voting()); + let gov_id = app.store_code(cw_core_contract()); + let cw20_id = app.store_code(cw20_contract()); + + let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { + root: CREATOR_ADDR.to_string(), + }; + let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { + token_info: dao_voting_cw20_balance::msg::TokenInfo::New { + code_id: cw20_id, + label: "DAO DAO voting".to_string(), + name: "DAO DAO".to_string(), + symbol: "DAO".to_string(), + decimals: 6, + initial_balances: vec![cw20::Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::from(2u64), + }], + marketing: None, + }, + }; + + // Instantiate the core module with an admin to do migrations. + let gov_instantiate = InstantiateMsg { + dao_uri: None, + admin: None, + name: "DAO DAO".to_string(), + description: "A DAO that builds DAOs.".to_string(), + image_url: None, + automatically_add_cw20s: false, + automatically_add_cw721s: false, + voting_module_instantiate_info: ModuleInstantiateInfo { + code_id: voting_id, + msg: to_json_binary(&voting_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "voting module".to_string(), + }, + proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "governance module".to_string(), + }], + initial_items: None, + }; + + let core_addr = app + .instantiate_contract( + gov_id, + Addr::unchecked(CREATOR_ADDR), + &gov_instantiate, + &[], + "cw-governance", + Some(CREATOR_ADDR.to_string()), + ) + .unwrap(); + + let state: DumpStateResponse = app + .wrap() + .query_wasm_smart(core_addr.clone(), &QueryMsg::DumpState {}) + .unwrap(); + + app.execute( + Addr::unchecked(CREATOR_ADDR), + CosmosMsg::Wasm(WasmMsg::Migrate { + contract_addr: core_addr.to_string(), + new_code_id: gov_id, + msg: to_json_binary(&MigrateMsg::FromCompatible {}).unwrap(), + }), + ) + .unwrap(); + + let new_state: DumpStateResponse = app + .wrap() + .query_wasm_smart(core_addr, &QueryMsg::DumpState {}) + .unwrap(); + + assert_eq!(new_state, state); +} + +#[test] +fn test_migrate_from_beta() { + use cw_core_v1 as v1; + + let mut app = App::default(); + let govmod_id = app.store_code(sudo_proposal_contract()); + let voting_id = app.store_code(cw20_balances_voting()); + let core_id = app.store_code(cw_core_contract()); + let v1_core_id = app.store_code(v1_cw_core_contract()); + let cw20_id = app.store_code(cw20_contract()); + + let proposal_instantiate = dao_proposal_sudo::msg::InstantiateMsg { + root: CREATOR_ADDR.to_string(), + }; + let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { + token_info: dao_voting_cw20_balance::msg::TokenInfo::New { + code_id: cw20_id, + label: "DAO DAO voting".to_string(), + name: "DAO DAO".to_string(), + symbol: "DAO".to_string(), + decimals: 6, + initial_balances: vec![cw20::Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::from(2u64), + }], + marketing: None, + }, + }; + + // Instantiate the core module with an admin to do migrations. + let v1_core_instantiate = v1::msg::InstantiateMsg { + admin: None, + name: "DAO DAO".to_string(), + description: "A DAO that builds DAOs.".to_string(), + image_url: None, + automatically_add_cw20s: false, + automatically_add_cw721s: false, + voting_module_instantiate_info: v1::msg::ModuleInstantiateInfo { + code_id: voting_id, + msg: to_json_binary(&voting_instantiate).unwrap(), + admin: v1::msg::Admin::CoreContract {}, + label: "voting module".to_string(), + }, + proposal_modules_instantiate_info: vec![ + v1::msg::ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&proposal_instantiate).unwrap(), + admin: v1::msg::Admin::CoreContract {}, + label: "governance module 1".to_string(), + }, + v1::msg::ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&proposal_instantiate).unwrap(), + admin: v1::msg::Admin::CoreContract {}, + label: "governance module 2".to_string(), + }, + ], + initial_items: None, + }; + + let core_addr = app + .instantiate_contract( + v1_core_id, + Addr::unchecked(CREATOR_ADDR), + &v1_core_instantiate, + &[], + "cw-governance", + Some(CREATOR_ADDR.to_string()), + ) + .unwrap(); + + app.execute( + Addr::unchecked(CREATOR_ADDR), + CosmosMsg::Wasm(WasmMsg::Migrate { + contract_addr: core_addr.to_string(), + new_code_id: core_id, + msg: to_json_binary(&MigrateMsg::FromV1 { + dao_uri: None, + params: None, + }) + .unwrap(), + }), + ) + .unwrap(); + + let new_state: DumpStateResponse = app + .wrap() + .query_wasm_smart(&core_addr, &QueryMsg::DumpState {}) + .unwrap(); + + let proposal_modules = new_state.proposal_modules; + assert_eq!(2, proposal_modules.len()); + for (idx, module) in proposal_modules.iter().enumerate() { + let prefix = derive_proposal_module_prefix(idx).unwrap(); + assert_eq!(prefix, module.prefix); + assert_eq!(ProposalModuleStatus::Enabled, module.status); + } + + // Check that we may not migrate more than once. + let err: ContractError = app + .execute( + Addr::unchecked(CREATOR_ADDR), + CosmosMsg::Wasm(WasmMsg::Migrate { + contract_addr: core_addr.to_string(), + new_code_id: core_id, + msg: to_json_binary(&MigrateMsg::FromV1 { + dao_uri: None, + params: None, + }) + .unwrap(), + }), + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, ContractError::AlreadyMigrated {}) +} + +#[test] +fn test_migrate_mock() { + let mut deps = mock_dependencies(); + let dao_uri: String = "/dao/uri".to_string(); + let msg = MigrateMsg::FromV1 { + dao_uri: Some(dao_uri.clone()), + params: None, + }; + let env = mock_env(); + + // Set starting version to v1. + set_contract_version(&mut deps.storage, CONTRACT_NAME, "0.1.0").unwrap(); + + // Write to storage in old proposal module format + let proposal_modules_key = Addr::unchecked("addr"); + let old_map: Map = Map::new("proposal_modules"); + let path = old_map.key(proposal_modules_key.clone()); + deps.storage.set(&path, &to_json_binary(&Empty {}).unwrap()); + + // Write to storage in old config format + #[cw_serde] + struct V1Config { + pub name: String, + pub description: String, + pub image_url: Option, + pub automatically_add_cw20s: bool, + pub automatically_add_cw721s: bool, + } + + let v1_config = V1Config { + name: "core dao".to_string(), + description: "a dao".to_string(), + image_url: None, + automatically_add_cw20s: false, + automatically_add_cw721s: false, + }; + + let config_item: Item = Item::new("config"); + config_item.save(&mut deps.storage, &v1_config).unwrap(); + + // Migrate to v2 + migrate(deps.as_mut(), env, msg).unwrap(); + + let new_path = PROPOSAL_MODULES.key(proposal_modules_key); + let prop_module_bytes = deps.storage.get(&new_path).unwrap(); + let module: ProposalModule = from_json(prop_module_bytes).unwrap(); + assert_eq!(module.address, Addr::unchecked("addr")); + assert_eq!(module.prefix, derive_proposal_module_prefix(0).unwrap()); + assert_eq!(module.status, ProposalModuleStatus::Enabled {}); + + let v2_config_item: Item = Item::new("config_v2"); + let v2_config = v2_config_item.load(&deps.storage).unwrap(); + assert_eq!(v2_config.dao_uri, Some(dao_uri)); + assert_eq!(v2_config.name, v1_config.name); + assert_eq!(v2_config.description, v1_config.description); + assert_eq!(v2_config.image_url, v1_config.image_url); + assert_eq!( + v2_config.automatically_add_cw20s, + v1_config.automatically_add_cw20s + ); + assert_eq!( + v2_config.automatically_add_cw721s, + v1_config.automatically_add_cw721s + ) +} + +#[test] +fn test_execute_stargate_msg() { + let (core_addr, mut app) = do_standard_instantiate(true, None); + let proposal_modules: Vec = app + .wrap() + .query_wasm_smart( + core_addr.clone(), + &QueryMsg::ProposalModules { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + assert_eq!(proposal_modules.len(), 1); + let proposal_module = proposal_modules.into_iter().next().unwrap(); + + let res = app.execute_contract( + proposal_module.address, + core_addr, + &ExecuteMsg::ExecuteProposalHook { + msgs: vec![CosmosMsg::Stargate { + type_url: "foo_type".to_string(), + value: to_json_binary("foo_bin").unwrap(), + }], + }, + &[], + ); + // TODO: Once cw-multi-test supports executing stargate/ibc messages we can change this test assert + assert!(res.is_err()); +} + +#[test] +fn test_module_prefixes() { + let mut app = App::default(); + let govmod_id = app.store_code(sudo_proposal_contract()); + let gov_id = app.store_code(cw_core_contract()); + + let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { + root: CREATOR_ADDR.to_string(), + }; + + let gov_instantiate = InstantiateMsg { + dao_uri: None, + admin: None, + name: "DAO DAO".to_string(), + description: "A DAO that builds DAOs.".to_string(), + image_url: None, + automatically_add_cw20s: true, + automatically_add_cw721s: true, + voting_module_instantiate_info: ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "voting module".to_string(), + }, + proposal_modules_instantiate_info: vec![ + ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "proposal module 1".to_string(), + }, + ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "proposal module 2".to_string(), + }, + ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), + funds: vec![], + label: "proposal module 2".to_string(), + }, + ], + initial_items: None, + }; + + let gov_addr = app + .instantiate_contract( + gov_id, + Addr::unchecked(CREATOR_ADDR), + &gov_instantiate, + &[], + "cw-governance", + None, + ) + .unwrap(); + + let modules: Vec = app + .wrap() + .query_wasm_smart( + gov_addr, + &QueryMsg::ProposalModules { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + assert_eq!(modules.len(), 3); + + let module_1 = &modules[0]; + assert_eq!(module_1.status, ProposalModuleStatus::Enabled {}); + assert_eq!(module_1.prefix, "A"); + assert_eq!(&module_1.address, &modules[0].address); + + let module_2 = &modules[1]; + assert_eq!(module_2.status, ProposalModuleStatus::Enabled {}); + assert_eq!(module_2.prefix, "B"); + assert_eq!(&module_2.address, &modules[1].address); + + let module_3 = &modules[2]; + assert_eq!(module_3.status, ProposalModuleStatus::Enabled {}); + assert_eq!(module_3.prefix, "C"); + assert_eq!(&module_3.address, &modules[2].address); +} + +fn get_active_modules(gov: &DaoDaoCore) -> Vec { + let modules = gov.proposal_modules(None, None).unwrap(); + + modules + .into_iter() + .filter(|module: &ProposalModule| module.status == ProposalModuleStatus::Enabled) + .collect() +} + +#[test] +fn test_add_remove_subdaos() { + let (core_addr, mut app) = do_standard_instantiate(false, None); + + test_unauthorized( + &mut app, + core_addr.clone(), + ExecuteMsg::UpdateSubDaos { + to_add: vec![], + to_remove: vec![], + }, + ); + + let to_add: Vec = vec![ + SubDao { + addr: "subdao001".to_string(), + charter: None, + }, + SubDao { + addr: "subdao002".to_string(), + charter: Some("cool charter bro".to_string()), + }, + SubDao { + addr: "subdao005".to_string(), + charter: None, + }, + SubDao { + addr: "subdao007".to_string(), + charter: None, + }, + ]; + let to_remove: Vec = vec![]; + + app.execute_contract( + Addr::unchecked(core_addr.clone()), + core_addr.clone(), + &ExecuteMsg::UpdateSubDaos { to_add, to_remove }, + &[], + ) + .unwrap(); + + let res: Vec = app + .wrap() + .query_wasm_smart( + core_addr.clone(), + &QueryMsg::ListSubDaos { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + assert_eq!(res.len(), 4); + + let to_remove: Vec = vec!["subdao005".to_string()]; + + app.execute_contract( + Addr::unchecked(core_addr.clone()), + core_addr.clone(), + &ExecuteMsg::UpdateSubDaos { + to_add: vec![], + to_remove, + }, + &[], + ) + .unwrap(); + + let res: Vec = app + .wrap() + .query_wasm_smart( + core_addr, + &QueryMsg::ListSubDaos { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + assert_eq!(res.len(), 3); + + let test_res: SubDao = SubDao { + addr: "subdao002".to_string(), + charter: Some("cool charter bro".to_string()), + }; + + assert_eq!(res[1], test_res); + + let full_result_set: Vec = vec![ + SubDao { + addr: "subdao001".to_string(), + charter: None, + }, + SubDao { + addr: "subdao002".to_string(), + charter: Some("cool charter bro".to_string()), + }, + SubDao { + addr: "subdao007".to_string(), + charter: None, + }, + ]; + + assert_eq!(res, full_result_set); +} + +#[test] +pub fn test_migrate_update_version() { + let mut deps = mock_dependencies(); + cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); + migrate(deps.as_mut(), mock_env(), MigrateMsg::FromCompatible {}).unwrap(); + let version = cw2::get_contract_version(&deps.storage).unwrap(); + assert_eq!(version.version, CONTRACT_VERSION); + assert_eq!(version.contract, CONTRACT_NAME); +} + +#[test] +fn test_query_info() { + let (core_addr, app) = do_standard_instantiate(true, None); + let res: InfoResponse = app + .wrap() + .query_wasm_smart(core_addr, &QueryMsg::Info {}) + .unwrap(); + assert_eq!( + res, + InfoResponse { + info: ContractVersion { + contract: CONTRACT_NAME.to_string(), + version: CONTRACT_VERSION.to_string() + } + } + ) +} diff --git a/contracts/dao-dao-core/src/lib.rs b/contracts/dao-dao-core/src/lib.rs index 20a9a57d7..e1d62aad4 100644 --- a/contracts/dao-dao-core/src/lib.rs +++ b/contracts/dao-dao-core/src/lib.rs @@ -4,6 +4,8 @@ pub mod contract; mod error; pub mod state; +#[cfg(test)] +mod cw_orch_tests; #[cfg(test)] mod tests; diff --git a/contracts/test/dao-proposal-sudo/Cargo.toml b/contracts/test/dao-proposal-sudo/Cargo.toml index f6e51f0de..87f102efc 100644 --- a/contracts/test/dao-proposal-sudo/Cargo.toml +++ b/contracts/test/dao-proposal-sudo/Cargo.toml @@ -24,6 +24,7 @@ cw2 = { workspace = true } thiserror = { workspace = true } dao-dao-macros = { workspace = true } dao-interface = { workspace = true } +cw-orch = "0.22.2" [dev-dependencies] cw-multi-test = { workspace = true } diff --git a/contracts/test/dao-proposal-sudo/src/msg.rs b/contracts/test/dao-proposal-sudo/src/msg.rs index 142148400..de00f94ad 100644 --- a/contracts/test/dao-proposal-sudo/src/msg.rs +++ b/contracts/test/dao-proposal-sudo/src/msg.rs @@ -7,12 +7,14 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { + #[fn_name("proposal_execute")] Execute { msgs: Vec }, } #[cw_serde] -#[derive(QueryResponses)] +#[derive(cw_orch::QueryFns, QueryResponses)] pub enum QueryMsg { #[returns(cosmwasm_std::Addr)] Admin {}, diff --git a/packages/cw-orch/Cargo.toml b/packages/cw-orch/Cargo.toml new file mode 100644 index 000000000..8c27bfdde --- /dev/null +++ b/packages/cw-orch/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "dao-cw-orch" +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cw-orch = { version = "0.22.2" } +cw20-stake = { version = "2.4.2", path = "../../contracts/staking/cw20-stake" } +cw20-stake-external-rewards = { version = "2.4.2", path = "../../contracts/staking/cw20-stake-external-rewards" } +cw20-stake-reward-distributor = { version = "2.4.2", path = "../../contracts/staking/cw20-stake-reward-distributor" } +dao-dao-core = { version = "2.4.2", path = "../../contracts/dao-dao-core" } +dao-interface = { version = "2.4.2", path = "../dao-interface" } +dao-pre-propose-approval-single = { version = "2.4.2", path = "../../contracts/pre-propose/dao-pre-propose-approval-single" } +dao-pre-propose-approver = { version = "2.4.2", path = "../../contracts/pre-propose/dao-pre-propose-approver" } +dao-pre-propose-multiple = { version = "2.4.2", path = "../../contracts/pre-propose/dao-pre-propose-multiple" } +dao-pre-propose-single = { version = "2.4.2", path = "../../contracts/pre-propose/dao-pre-propose-single" } +dao-proposal-condorcet = { version = "2.4.2", path = "../../contracts/proposal/dao-proposal-condorcet" } +dao-proposal-hook-counter = { version = "2.4.2", path = "../../contracts/test/dao-proposal-hook-counter" } +dao-proposal-multiple = { version = "2.4.2", path = "../../contracts/proposal/dao-proposal-multiple" } +dao-proposal-single = { version = "2.4.2", path = "../../contracts/proposal/dao-proposal-single" } +dao-proposal-sudo = { version = "2.4.2", path = "../../contracts/test/dao-proposal-sudo" } +dao-test-custom-factory = { version = "2.4.2", path = "../../contracts/test/dao-test-custom-factory" } +dao-voting-cw20-balance = { version = "2.4.2", path = "../../contracts/test/dao-voting-cw20-balance" } +dao-voting-cw20-staked = { version = "2.4.2", path = "../../contracts/voting/dao-voting-cw20-staked" } +dao-voting-cw4 = { version = "2.4.2", path = "../../contracts/voting/dao-voting-cw4" } +dao-voting-cw721-roles = { version = "2.4.2", path = "../../contracts/voting/dao-voting-cw721-roles" } +dao-voting-cw721-staked = { version = "2.4.2", path = "../../contracts/voting/dao-voting-cw721-staked" } +dao-voting-token-staked = { version = "2.4.2", path = "../../contracts/voting/dao-voting-token-staked" } diff --git a/packages/cw-orch/src/core.rs b/packages/cw-orch/src/core.rs new file mode 100644 index 000000000..501d56f4e --- /dev/null +++ b/packages/cw-orch/src/core.rs @@ -0,0 +1,24 @@ +use cw_orch::{interface, prelude::*}; + +use dao_dao_core::contract::{execute, instantiate, migrate, query, reply}; +use dao_interface::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] +pub struct DaoDaoCore; + +impl Uploadable for DaoDaoCore { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_dao_core") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new( + ContractWrapper::new_with_empty(execute, instantiate, query) + .with_migrate(migrate) + .with_reply(reply), + ) + } +} diff --git a/packages/cw-orch/src/lib.rs b/packages/cw-orch/src/lib.rs new file mode 100644 index 000000000..9d99e4775 --- /dev/null +++ b/packages/cw-orch/src/lib.rs @@ -0,0 +1,16 @@ +mod core; +mod pre_propose; +mod proposal; +mod staking; +mod test_contracts; +mod voting; + +pub use core::*; +pub use pre_propose::*; +pub use proposal::*; +pub use staking::*; +pub use test_contracts::*; +pub use voting::*; + +#[cfg(test)] +pub mod tests; diff --git a/packages/cw-orch/src/pre_propose/approval_single.rs b/packages/cw-orch/src/pre_propose/approval_single.rs new file mode 100644 index 000000000..98b9927f6 --- /dev/null +++ b/packages/cw-orch/src/pre_propose/approval_single.rs @@ -0,0 +1,20 @@ +use cw_orch::{interface, prelude::*}; + +use dao_pre_propose_approval_single::contract::{execute, instantiate, query}; +use dao_pre_propose_approval_single::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] +pub struct DaoPreProposeApprovalSingle; + +impl Uploadable for DaoPreProposeApprovalSingle { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_pre_propose_approval_single") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query)) + } +} diff --git a/packages/cw-orch/src/pre_propose/approver.rs b/packages/cw-orch/src/pre_propose/approver.rs new file mode 100644 index 000000000..cebd7c13a --- /dev/null +++ b/packages/cw-orch/src/pre_propose/approver.rs @@ -0,0 +1,20 @@ +use cw_orch::{interface, prelude::*}; + +use dao_pre_propose_approver::contract::{execute, instantiate, query}; +use dao_pre_propose_approver::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] +pub struct DaoPreProposeApprover; + +impl Uploadable for DaoPreProposeApprover { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_pre_propose_approver") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query)) + } +} diff --git a/packages/cw-orch/src/pre_propose/mod.rs b/packages/cw-orch/src/pre_propose/mod.rs new file mode 100644 index 000000000..64ccef2f2 --- /dev/null +++ b/packages/cw-orch/src/pre_propose/mod.rs @@ -0,0 +1,9 @@ +mod approval_single; +mod approver; +mod multiple; +mod single; + +pub use approval_single::DaoPreProposeApprovalSingle; +pub use approver::DaoPreProposeApprover; +pub use multiple::DaoPreProposeMultiple; +pub use single::DaoPreProposeSingle; diff --git a/packages/cw-orch/src/pre_propose/multiple.rs b/packages/cw-orch/src/pre_propose/multiple.rs new file mode 100644 index 000000000..7517df320 --- /dev/null +++ b/packages/cw-orch/src/pre_propose/multiple.rs @@ -0,0 +1,20 @@ +use cw_orch::{interface, prelude::*}; + +use dao_pre_propose_multiple::contract::{execute, instantiate, query}; +use dao_pre_propose_multiple::contract::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] +pub struct DaoPreProposeMultiple; + +impl Uploadable for DaoPreProposeMultiple { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_pre_propose_multiple") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query)) + } +} diff --git a/packages/cw-orch/src/pre_propose/single.rs b/packages/cw-orch/src/pre_propose/single.rs new file mode 100644 index 000000000..891654455 --- /dev/null +++ b/packages/cw-orch/src/pre_propose/single.rs @@ -0,0 +1,20 @@ +use cw_orch::{interface, prelude::*}; + +use dao_pre_propose_single::contract::{execute, instantiate, query}; +use dao_pre_propose_single::contract::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] +pub struct DaoPreProposeSingle; + +impl Uploadable for DaoPreProposeSingle { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_pre_propose_single") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query)) + } +} diff --git a/packages/cw-orch/src/proposal/condorcet.rs b/packages/cw-orch/src/proposal/condorcet.rs new file mode 100644 index 000000000..fdfb8fb50 --- /dev/null +++ b/packages/cw-orch/src/proposal/condorcet.rs @@ -0,0 +1,20 @@ +use cw_orch::{interface, prelude::*}; + +use dao_proposal_condorcet::contract::{execute, instantiate, query, reply}; +use dao_proposal_condorcet::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] +pub struct DaoProposalCondorcet; + +impl Uploadable for DaoProposalCondorcet { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_proposal_condorcet") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query).with_reply(reply)) + } +} diff --git a/packages/cw-orch/src/proposal/mod.rs b/packages/cw-orch/src/proposal/mod.rs new file mode 100644 index 000000000..faf0b7796 --- /dev/null +++ b/packages/cw-orch/src/proposal/mod.rs @@ -0,0 +1,7 @@ +mod condorcet; +mod multiple; +mod single; + +pub use condorcet::DaoProposalCondorcet; +pub use multiple::DaoProposalMultiple; +pub use single::DaoProposalSingle; diff --git a/packages/cw-orch/src/proposal/multiple.rs b/packages/cw-orch/src/proposal/multiple.rs new file mode 100644 index 000000000..dc28e8ef0 --- /dev/null +++ b/packages/cw-orch/src/proposal/multiple.rs @@ -0,0 +1,24 @@ +use cw_orch::{interface, prelude::*}; + +use dao_proposal_multiple::contract::{execute, instantiate, migrate, query, reply}; +use dao_proposal_multiple::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] +pub struct DaoProposalMultiple; + +impl Uploadable for DaoProposalMultiple { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_proposal_multiple") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new( + ContractWrapper::new_with_empty(execute, instantiate, query) + .with_reply(reply) + .with_migrate(migrate), + ) + } +} diff --git a/packages/cw-orch/src/proposal/single.rs b/packages/cw-orch/src/proposal/single.rs new file mode 100644 index 000000000..7db09ee3b --- /dev/null +++ b/packages/cw-orch/src/proposal/single.rs @@ -0,0 +1,24 @@ +use cw_orch::{interface, prelude::*}; + +use dao_proposal_single::contract::{execute, instantiate, migrate, query, reply}; +use dao_proposal_single::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] +pub struct DaoProposalSingle; + +impl Uploadable for DaoProposalSingle { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_proposal_single") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new( + ContractWrapper::new_with_empty(execute, instantiate, query) + .with_reply(reply) + .with_migrate(migrate), + ) + } +} diff --git a/packages/cw-orch/src/staking/cw20_stake.rs b/packages/cw-orch/src/staking/cw20_stake.rs new file mode 100644 index 000000000..8b43d844c --- /dev/null +++ b/packages/cw-orch/src/staking/cw20_stake.rs @@ -0,0 +1,20 @@ +use cw_orch::{interface, prelude::*}; + +use cw20_stake::contract::{execute, instantiate, migrate, query}; +use cw20_stake::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] +pub struct Cw20Stake; + +impl Uploadable for Cw20Stake { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("cw20_stake") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query).with_migrate(migrate)) + } +} diff --git a/packages/cw-orch/src/staking/external_rewards.rs b/packages/cw-orch/src/staking/external_rewards.rs new file mode 100644 index 000000000..0d4f19c5c --- /dev/null +++ b/packages/cw-orch/src/staking/external_rewards.rs @@ -0,0 +1,20 @@ +use cw_orch::{interface, prelude::*}; + +use cw20_stake_external_rewards::contract::{execute, instantiate, migrate, query}; +use cw20_stake_external_rewards::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] +pub struct Cw20StakeExternalRewards; + +impl Uploadable for Cw20StakeExternalRewards { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("cw20_stake_external_rewards") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query).with_migrate(migrate)) + } +} diff --git a/packages/cw-orch/src/staking/mod.rs b/packages/cw-orch/src/staking/mod.rs new file mode 100644 index 000000000..41808494c --- /dev/null +++ b/packages/cw-orch/src/staking/mod.rs @@ -0,0 +1,7 @@ +mod cw20_stake; +mod external_rewards; +mod reward_distributor; + +pub use cw20_stake::Cw20Stake; +pub use external_rewards::Cw20StakeExternalRewards; +pub use reward_distributor::Cw20StakeRewardDistributor; diff --git a/packages/cw-orch/src/staking/reward_distributor.rs b/packages/cw-orch/src/staking/reward_distributor.rs new file mode 100644 index 000000000..2f398ca81 --- /dev/null +++ b/packages/cw-orch/src/staking/reward_distributor.rs @@ -0,0 +1,20 @@ +use cw_orch::{interface, prelude::*}; + +use cw20_stake_reward_distributor::contract::{execute, instantiate, migrate, query}; +use cw20_stake_reward_distributor::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] +pub struct Cw20StakeRewardDistributor; + +impl Uploadable for Cw20StakeRewardDistributor { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("cw20_stake_reward_distributor") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query).with_migrate(migrate)) + } +} diff --git a/packages/cw-orch/src/test_contracts/mod.rs b/packages/cw-orch/src/test_contracts/mod.rs new file mode 100644 index 000000000..ac82e1634 --- /dev/null +++ b/packages/cw-orch/src/test_contracts/mod.rs @@ -0,0 +1,9 @@ +mod proposal_hook_counter; +mod proposal_sudo; +mod test_custom_factory; +mod voting_cw20_balance; + +pub use proposal_hook_counter::DaoProposalHookCounter; +pub use proposal_sudo::DaoProposalSudo; +pub use test_custom_factory::DaoTestCustomFactory; +pub use voting_cw20_balance::DaoVotingCw20Balance; diff --git a/packages/cw-orch/src/test_contracts/proposal_hook_counter.rs b/packages/cw-orch/src/test_contracts/proposal_hook_counter.rs new file mode 100644 index 000000000..7dc26654e --- /dev/null +++ b/packages/cw-orch/src/test_contracts/proposal_hook_counter.rs @@ -0,0 +1,20 @@ +use cw_orch::{interface, prelude::*}; + +use dao_proposal_hook_counter::contract::{execute, instantiate, query}; +use dao_proposal_hook_counter::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] +pub struct DaoProposalHookCounter; + +impl Uploadable for DaoProposalHookCounter { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_proposal_hook_counter") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query)) + } +} diff --git a/packages/cw-orch/src/test_contracts/proposal_sudo.rs b/packages/cw-orch/src/test_contracts/proposal_sudo.rs new file mode 100644 index 000000000..bd623624a --- /dev/null +++ b/packages/cw-orch/src/test_contracts/proposal_sudo.rs @@ -0,0 +1,20 @@ +use cw_orch::{interface, prelude::*}; + +use dao_proposal_sudo::contract::{execute, instantiate, query}; +use dao_proposal_sudo::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] +pub struct DaoProposalSudo; + +impl Uploadable for DaoProposalSudo { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_proposal_sudo") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query)) + } +} diff --git a/packages/cw-orch/src/test_contracts/test_custom_factory.rs b/packages/cw-orch/src/test_contracts/test_custom_factory.rs new file mode 100644 index 000000000..3424df887 --- /dev/null +++ b/packages/cw-orch/src/test_contracts/test_custom_factory.rs @@ -0,0 +1,20 @@ +use cw_orch::{interface, prelude::*}; + +use dao_test_custom_factory::contract::{execute, instantiate, query, reply}; +use dao_test_custom_factory::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] +pub struct DaoTestCustomFactory; + +impl Uploadable for DaoTestCustomFactory { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_test_custom_factory") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query).with_reply(reply)) + } +} diff --git a/packages/cw-orch/src/test_contracts/voting_cw20_balance.rs b/packages/cw-orch/src/test_contracts/voting_cw20_balance.rs new file mode 100644 index 000000000..fdf7b84c0 --- /dev/null +++ b/packages/cw-orch/src/test_contracts/voting_cw20_balance.rs @@ -0,0 +1,20 @@ +use cw_orch::{interface, prelude::*}; + +use dao_voting_cw20_balance::contract::{execute, instantiate, query, reply}; +use dao_voting_cw20_balance::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] +pub struct DaoVotingCw20Balance; + +impl Uploadable for DaoVotingCw20Balance { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_voting_cw20_balance") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query).with_reply(reply)) + } +} diff --git a/packages/cw-orch/src/tests.rs b/packages/cw-orch/src/tests.rs new file mode 100644 index 000000000..82d254fb2 --- /dev/null +++ b/packages/cw-orch/src/tests.rs @@ -0,0 +1,69 @@ +use std::collections::HashSet; + +use cw_orch::{ + contract::interface_traits::Uploadable, + environment::{ChainInfo, ChainKind, NetworkInfo}, + mock::Mock, +}; + +use crate::{ + Cw20Stake, Cw20StakeExternalRewards, Cw20StakeRewardDistributor, DaoDaoCore,DaoVotingCw20Balance, + DaoPreProposeApprovalSingle, DaoPreProposeApprover, DaoPreProposeMultiple, DaoPreProposeSingle, + DaoProposalCondorcet, DaoProposalHookCounter, DaoProposalMultiple, DaoProposalSingle, + DaoProposalSudo, DaoTestCustomFactory, DaoVotingCw20Staked, DaoVotingCw4, DaoVotingCw721Roles, + DaoVotingCw721Staked, DaoVotingTokenStaked, +}; + +pub const DUMMY_CHAIN_INFO: ChainInfo = ChainInfo { + chain_id: "mock-1", + gas_denom: "none", + gas_price: 0.0, + grpc_urls: &[], + lcd_url: None, + fcd_url: None, + network_info: NetworkInfo { + chain_name: "mock", + pub_address_prefix: "mock", + coin_type: 118, + }, + kind: ChainKind::Local, +}; + +#[test] +fn test_all_wasms_different() { + let all_paths = vec![ + // CORE + DaoDaoCore::::wasm(&DUMMY_CHAIN_INFO.into()), + // PRE-PROPOSE + DaoPreProposeApprovalSingle::::wasm(&DUMMY_CHAIN_INFO.into()), + DaoPreProposeApprover::::wasm(&DUMMY_CHAIN_INFO.into()), + DaoPreProposeMultiple::::wasm(&DUMMY_CHAIN_INFO.into()), + DaoPreProposeSingle::::wasm(&DUMMY_CHAIN_INFO.into()), + // PROPOSAL + DaoProposalCondorcet::::wasm(&DUMMY_CHAIN_INFO.into()), + DaoProposalMultiple::::wasm(&DUMMY_CHAIN_INFO.into()), + DaoProposalSingle::::wasm(&DUMMY_CHAIN_INFO.into()), + // Stake + Cw20Stake::::wasm(&DUMMY_CHAIN_INFO.into()), + Cw20StakeExternalRewards::::wasm(&DUMMY_CHAIN_INFO.into()), + Cw20StakeRewardDistributor::::wasm(&DUMMY_CHAIN_INFO.into()), + // Voting + DaoVotingCw4::::wasm(&DUMMY_CHAIN_INFO.into()), + DaoVotingCw20Staked::::wasm(&DUMMY_CHAIN_INFO.into()), + DaoVotingCw721Staked::::wasm(&DUMMY_CHAIN_INFO.into()), + DaoVotingCw721Roles::::wasm(&DUMMY_CHAIN_INFO.into()), + DaoVotingTokenStaked::::wasm(&DUMMY_CHAIN_INFO.into()), + // Test + DaoProposalHookCounter::::wasm(&DUMMY_CHAIN_INFO.into()), + DaoProposalSudo::::wasm(&DUMMY_CHAIN_INFO.into()), + DaoTestCustomFactory::::wasm(&DUMMY_CHAIN_INFO.into()), + DaoVotingCw20Balance::::wasm(&DUMMY_CHAIN_INFO.into()), + ]; + let all_paths: Vec<_> = all_paths + .into_iter() + .map(|path| path.path().as_os_str().to_string_lossy().to_string()) + .collect(); + + let mut uniq = HashSet::new(); + assert!(all_paths.into_iter().all(move |x| uniq.insert(x))); +} diff --git a/packages/cw-orch/src/voting/cw20_staked.rs b/packages/cw-orch/src/voting/cw20_staked.rs new file mode 100644 index 000000000..1f044a913 --- /dev/null +++ b/packages/cw-orch/src/voting/cw20_staked.rs @@ -0,0 +1,24 @@ +use cw_orch::{interface, prelude::*}; + +use dao_voting_cw20_staked::contract::{execute, instantiate, migrate, query, reply}; +use dao_voting_cw20_staked::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] +pub struct DaoVotingCw20Staked; + +impl Uploadable for DaoVotingCw20Staked { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_voting_cw20_staked") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new( + ContractWrapper::new_with_empty(execute, instantiate, query) + .with_reply(reply) + .with_migrate(migrate), + ) + } +} diff --git a/packages/cw-orch/src/voting/cw4.rs b/packages/cw-orch/src/voting/cw4.rs new file mode 100644 index 000000000..11dfac5ea --- /dev/null +++ b/packages/cw-orch/src/voting/cw4.rs @@ -0,0 +1,24 @@ +use cw_orch::{interface, prelude::*}; + +use dao_voting_cw4::contract::{execute, instantiate, migrate, query, reply}; +use dao_voting_cw4::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] +pub struct DaoVotingCw4; + +impl Uploadable for DaoVotingCw4 { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_voting_cw4") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new( + ContractWrapper::new_with_empty(execute, instantiate, query) + .with_reply(reply) + .with_migrate(migrate), + ) + } +} diff --git a/packages/cw-orch/src/voting/cw721_roles.rs b/packages/cw-orch/src/voting/cw721_roles.rs new file mode 100644 index 000000000..6469a0887 --- /dev/null +++ b/packages/cw-orch/src/voting/cw721_roles.rs @@ -0,0 +1,20 @@ +use cw_orch::{interface, prelude::*}; + +use dao_voting_cw721_roles::contract::{execute, instantiate, query, reply}; +use dao_voting_cw721_roles::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] +pub struct DaoVotingCw721Roles; + +impl Uploadable for DaoVotingCw721Roles { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_voting_cw721_roles") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query).with_reply(reply)) + } +} diff --git a/packages/cw-orch/src/voting/cw721_staked.rs b/packages/cw-orch/src/voting/cw721_staked.rs new file mode 100644 index 000000000..5ed2f1ce1 --- /dev/null +++ b/packages/cw-orch/src/voting/cw721_staked.rs @@ -0,0 +1,24 @@ +use cw_orch::{interface, prelude::*}; + +use dao_voting_cw721_staked::contract::{execute, instantiate, migrate, query, reply}; +use dao_voting_cw721_staked::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] +pub struct DaoVotingCw721Staked; + +impl Uploadable for DaoVotingCw721Staked { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_voting_cw721_staked") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new( + ContractWrapper::new_with_empty(execute, instantiate, query) + .with_reply(reply) + .with_migrate(migrate), + ) + } +} diff --git a/packages/cw-orch/src/voting/mod.rs b/packages/cw-orch/src/voting/mod.rs new file mode 100644 index 000000000..41e697927 --- /dev/null +++ b/packages/cw-orch/src/voting/mod.rs @@ -0,0 +1,11 @@ +mod cw20_staked; +mod cw4; +mod cw721_roles; +mod cw721_staked; +mod token_staked; + +pub use cw20_staked::DaoVotingCw20Staked; +pub use cw4::DaoVotingCw4; +pub use cw721_roles::DaoVotingCw721Roles; +pub use cw721_staked::DaoVotingCw721Staked; +pub use token_staked::DaoVotingTokenStaked; diff --git a/packages/cw-orch/src/voting/token_staked.rs b/packages/cw-orch/src/voting/token_staked.rs new file mode 100644 index 000000000..a8f41ca94 --- /dev/null +++ b/packages/cw-orch/src/voting/token_staked.rs @@ -0,0 +1,24 @@ +use cw_orch::{interface, prelude::*}; + +use dao_voting_token_staked::contract::{execute, instantiate, migrate, query, reply}; +use dao_voting_token_staked::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] +pub struct DaoVotingTokenStaked; + +impl Uploadable for DaoVotingTokenStaked { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_voting_token_staked") + .unwrap() + } + /// Returns a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new( + ContractWrapper::new_with_empty(execute, instantiate, query) + .with_reply(reply) + .with_migrate(migrate), + ) + } +} diff --git a/packages/dao-interface/Cargo.toml b/packages/dao-interface/Cargo.toml index 643c84df3..62252d894 100644 --- a/packages/dao-interface/Cargo.toml +++ b/packages/dao-interface/Cargo.toml @@ -15,6 +15,7 @@ cw20 = { workspace = true } cw721 = { workspace = true } cw-utils = { workspace = true } osmosis-std = { workspace = true } +cw-orch = "0.22.2" [dev-dependencies] cosmwasm-schema = { workspace = true } diff --git a/packages/dao-interface/src/lib.rs b/packages/dao-interface/src/lib.rs index aae0678fd..d410b5b68 100644 --- a/packages/dao-interface/src/lib.rs +++ b/packages/dao-interface/src/lib.rs @@ -8,3 +8,6 @@ pub mod query; pub mod state; pub mod token; pub mod voting; + +pub use msg::ExecuteMsgFns as CoreExecuteMsgFns; +pub use msg::QueryMsgFns as CoreQueryMsgFns; diff --git a/packages/dao-interface/src/msg.rs b/packages/dao-interface/src/msg.rs index 969288433..3e650b8ce 100644 --- a/packages/dao-interface/src/msg.rs +++ b/packages/dao-interface/src/msg.rs @@ -53,6 +53,7 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { /// Callable by the Admin, if one is configured. /// Executes messages in order. @@ -134,7 +135,7 @@ pub enum ExecuteMsg { } #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg { /// Get's the DAO's admin. Returns `Addr`. #[returns(cosmwasm_std::Addr)]