From b01de1cf971b2796a85bb235cfd721627a3b3c92 Mon Sep 17 00:00:00 2001 From: Kayanski Date: Thu, 16 May 2024 14:41:02 +0000 Subject: [PATCH 1/8] First test modifications --- Cargo.lock | 620 +++- contracts/dao-dao-core/Cargo.toml | 3 + contracts/dao-dao-core/src/cw_orch_tests.rs | 3068 +++++++++++++++++ contracts/dao-dao-core/src/lib.rs | 2 + contracts/test/dao-proposal-sudo/Cargo.toml | 1 + contracts/test/dao-proposal-sudo/src/msg.rs | 4 +- packages/cw-orch/Cargo.toml | 32 + packages/cw-orch/src/core.rs | 24 + packages/cw-orch/src/lib.rs | 16 + .../src/pre_propose/approval_single.rs | 20 + packages/cw-orch/src/pre_propose/approver.rs | 20 + packages/cw-orch/src/pre_propose/mod.rs | 9 + packages/cw-orch/src/pre_propose/multiple.rs | 20 + packages/cw-orch/src/pre_propose/single.rs | 20 + packages/cw-orch/src/proposal/condorcet.rs | 20 + packages/cw-orch/src/proposal/mod.rs | 7 + packages/cw-orch/src/proposal/multiple.rs | 24 + packages/cw-orch/src/proposal/single.rs | 24 + packages/cw-orch/src/staking/cw20_stake.rs | 20 + .../cw-orch/src/staking/external_rewards.rs | 20 + packages/cw-orch/src/staking/mod.rs | 7 + .../cw-orch/src/staking/reward_distributor.rs | 20 + packages/cw-orch/src/test_contracts/mod.rs | 9 + .../test_contracts/proposal_hook_counter.rs | 20 + .../src/test_contracts/proposal_sudo.rs | 20 + .../src/test_contracts/test_custom_factory.rs | 20 + .../src/test_contracts/voting_cw20_balance.rs | 20 + packages/cw-orch/src/tests.rs | 69 + packages/cw-orch/src/voting/cw20_staked.rs | 24 + packages/cw-orch/src/voting/cw4.rs | 24 + packages/cw-orch/src/voting/cw721_roles.rs | 20 + packages/cw-orch/src/voting/cw721_staked.rs | 24 + packages/cw-orch/src/voting/mod.rs | 11 + packages/cw-orch/src/voting/token_staked.rs | 24 + packages/dao-interface/Cargo.toml | 1 + packages/dao-interface/src/lib.rs | 3 + packages/dao-interface/src/msg.rs | 3 +- 37 files changed, 4244 insertions(+), 49 deletions(-) create mode 100644 contracts/dao-dao-core/src/cw_orch_tests.rs create mode 100644 packages/cw-orch/Cargo.toml create mode 100644 packages/cw-orch/src/core.rs create mode 100644 packages/cw-orch/src/lib.rs create mode 100644 packages/cw-orch/src/pre_propose/approval_single.rs create mode 100644 packages/cw-orch/src/pre_propose/approver.rs create mode 100644 packages/cw-orch/src/pre_propose/mod.rs create mode 100644 packages/cw-orch/src/pre_propose/multiple.rs create mode 100644 packages/cw-orch/src/pre_propose/single.rs create mode 100644 packages/cw-orch/src/proposal/condorcet.rs create mode 100644 packages/cw-orch/src/proposal/mod.rs create mode 100644 packages/cw-orch/src/proposal/multiple.rs create mode 100644 packages/cw-orch/src/proposal/single.rs create mode 100644 packages/cw-orch/src/staking/cw20_stake.rs create mode 100644 packages/cw-orch/src/staking/external_rewards.rs create mode 100644 packages/cw-orch/src/staking/mod.rs create mode 100644 packages/cw-orch/src/staking/reward_distributor.rs create mode 100644 packages/cw-orch/src/test_contracts/mod.rs create mode 100644 packages/cw-orch/src/test_contracts/proposal_hook_counter.rs create mode 100644 packages/cw-orch/src/test_contracts/proposal_sudo.rs create mode 100644 packages/cw-orch/src/test_contracts/test_custom_factory.rs create mode 100644 packages/cw-orch/src/test_contracts/voting_cw20_balance.rs create mode 100644 packages/cw-orch/src/tests.rs create mode 100644 packages/cw-orch/src/voting/cw20_staked.rs create mode 100644 packages/cw-orch/src/voting/cw4.rs create mode 100644 packages/cw-orch/src/voting/cw721_roles.rs create mode 100644 packages/cw-orch/src/voting/cw721_staked.rs create mode 100644 packages/cw-orch/src/voting/mod.rs create mode 100644 packages/cw-orch/src/voting/token_staked.rs diff --git a/Cargo.lock b/Cargo.lock index 6742e170e..f92677367 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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65b5158346ff6bc51724b6f48cd79e4cc68013909f6da6b251562ae056d6afb7" +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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d44ef3855f2bc3a39c1ac86c81f75cf7e374c1082599ccc315302e5d930f30b3" +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.21.0" @@ -39,9 +306,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" [[package]] name = "assert_matches" @@ -68,7 +335,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -79,7 +346,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[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.39", + "syn 2.0.63", "which", ] @@ -257,9 +530,9 @@ dependencies = [ [[package]] name = "bnum" -version = "0.8.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab9008b6bb9fc80b5277f2fe481c09e828743d9151203e804583eb4c9e15b31d" +checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" [[package]] name = "bootstrap-env" @@ -383,6 +656,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +[[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" @@ -466,6 +748,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" @@ -510,23 +803,23 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.5.0" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8bb3c77c3b7ce472056968c745eb501c440fbc07be5004eba02782c35bfbbe3" +checksum = "dd50718a2b6830ce9eb5d465de5a018a12e71729d66b70807ce97e6dd14f931d" dependencies = [ "digest 0.10.7", "ecdsa 0.16.9", "ed25519-zebra", - "k256 0.13.2", + "k256 0.13.1", "rand_core 0.6.4", "thiserror", ] [[package]] name = "cosmwasm-derive" -version = "1.5.0" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea73e9162e6efde00018d55ed0061e93a108b5d6ec4548b4f8ce3c706249687" +checksum = "242e98e7a231c122e08f300d9db3262d1007b51758a8732cd6210b3e9faa4f3a" dependencies = [ "syn 1.0.109", ] @@ -557,12 +850,12 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.5.0" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04d6864742e3a7662d024b51a94ea81c9af21db6faea2f9a6d2232bb97c6e53e" +checksum = "78c1556156fdf892a55cced6115968b961eaaadd6f724a2c2cb7d1e168e32dd3" dependencies = [ "base64 0.21.5", - "bech32", + "bech32 0.9.1", "bnum", "cosmwasm-crypto", "cosmwasm-derive", @@ -848,6 +1141,95 @@ 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.63", +] + +[[package]] +name = "cw-orch-core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c8e62b4d04699596bc2f45c57a8e5459495584d8127588ae4bbe7f95d616b61" +dependencies = [ + "abstract-cw-multi-test", + "anyhow", + "cosmos-sdk-proto 0.21.1", + "cosmwasm-std", + "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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6426c05377f8549d446ab5ca4a554cfb9f501eb5002edb9a73ceae7fd65822e1" +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" @@ -1270,6 +1652,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" @@ -1572,6 +1973,34 @@ dependencies = [ "thiserror", ] +[[package]] +name = "dao-cw-orch" +version = "2.4.2" +dependencies = [ + "cw-orch", + "cw20-stake 2.4.2", + "cw20-stake-external-rewards", + "cw20-stake-reward-distributor", + "dao-dao-core", + "dao-interface", + "dao-pre-propose-approval-single", + "dao-pre-propose-approver", + "dao-pre-propose-multiple", + "dao-pre-propose-single", + "dao-proposal-condorcet", + "dao-proposal-hook-counter", + "dao-proposal-multiple", + "dao-proposal-single", + "dao-proposal-sudo", + "dao-test-custom-factory", + "dao-voting-cw20-balance", + "dao-voting-cw20-staked", + "dao-voting-cw4", + "dao-voting-cw721-roles", + "dao-voting-cw721-staked", + "dao-voting-token-staked", +] + [[package]] name = "dao-cw721-extensions" version = "2.4.2" @@ -1586,10 +2015,12 @@ dependencies = [ name = "dao-dao-core" version = "2.4.2" dependencies = [ + "abstract-cw-plus-interface", "cosmwasm-schema", "cosmwasm-std", "cw-core", "cw-multi-test", + "cw-orch", "cw-paginate-storage 2.4.2", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -1598,6 +2029,7 @@ dependencies = [ "cw20-base 1.1.2", "cw721 0.18.0", "cw721-base 0.18.0", + "dao-cw-orch", "dao-dao-macros", "dao-interface", "dao-proposal-sudo", @@ -1637,6 +2069,7 @@ version = "2.4.2" dependencies = [ "cosmwasm-schema", "cosmwasm-std", + "cw-orch", "cw-utils 1.0.3", "cw2 1.1.2", "cw20 1.1.2", @@ -1924,6 +2357,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", + "cw-orch", "cw-storage-plus 1.2.0", "cw2 1.1.2", "dao-dao-macros", @@ -2210,6 +2644,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" @@ -2484,7 +2939,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -2888,6 +3343,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -2929,9 +3393,9 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" dependencies = [ "cfg-if", "ecdsa 0.16.9", @@ -2978,6 +3442,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.4.1", + "libc", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -3061,6 +3535,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.63", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -3116,6 +3601,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" @@ -3264,7 +3755,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -3295,7 +3786,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -3343,14 +3834,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" dependencies = [ "unicode-ident", ] @@ -3410,7 +3901,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -3433,9 +3924,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -3476,6 +3967,17 @@ 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.2" @@ -3755,9 +4257,9 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" dependencies = [ "serde_derive", ] @@ -3791,13 +4293,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -3830,7 +4332,7 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -4064,9 +4566,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" dependencies = [ "proc-macro2", "quote", @@ -4177,7 +4679,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", @@ -4195,7 +4697,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", @@ -4213,7 +4715,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", @@ -4223,6 +4725,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" @@ -4338,22 +4858,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -4423,7 +4943,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -4582,7 +5102,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] [[package]] @@ -4643,6 +5163,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.9" @@ -4736,7 +5262,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", "wasm-bindgen-shared", ] @@ -4758,7 +5284,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5011,5 +5537,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.63", ] diff --git a/contracts/dao-dao-core/Cargo.toml b/contracts/dao-dao-core/Cargo.toml index ac8e2060d..18c3da829 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, features = ["stargate"] } +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)] From 5883c87f5b9822fbeae59c0bcdc335711a32195c Mon Sep 17 00:00:00 2001 From: Kayanski Date: Thu, 16 May 2024 17:24:41 +0000 Subject: [PATCH 2/8] Added tests with cw-orch --- contracts/dao-dao-core/src/cw_orch_tests.rs | 550 ++++++++------------ 1 file changed, 203 insertions(+), 347 deletions(-) diff --git a/contracts/dao-dao-core/src/cw_orch_tests.rs b/contracts/dao-dao-core/src/cw_orch_tests.rs index ebfd8eb58..1bc816c65 100644 --- a/contracts/dao-dao-core/src/cw_orch_tests.rs +++ b/contracts/dao-dao-core/src/cw_orch_tests.rs @@ -15,7 +15,8 @@ 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_cw_orch::{DaoDaoCore, DaoProposalSudo, DaoVotingCw20Balance}; +use dao_interface::CoreExecuteMsgFns; use dao_interface::CoreQueryMsgFns; use dao_interface::{ msg::{ExecuteMsg, InitialItem, InstantiateMsg, MigrateMsg, QueryMsg}, @@ -27,7 +28,6 @@ use dao_interface::{ voting::{InfoResponse, VotingPowerAtHeightResponse}, }; use dao_proposal_sudo::msg::ExecuteMsgFns as _; - const CREATOR_ADDR: &str = "creator"; fn cw20_contract() -> Box> { @@ -540,9 +540,12 @@ fn test_removed_modules_can_not_execute() { .into()]) .unwrap_err(); - println!("{}", err); - assert!(format!("{:?}", err) - .contains("Proposal module with address is disabled and cannot execute messages")); + assert!(format!("{:?}", err).contains( + &ContractError::ModuleDisabledCannotExecute { + address: Addr::unchecked("") + } + .to_string() + )); // Check that the enabled query works. let enabled_modules = gov.active_proposal_modules(None, None).unwrap(); @@ -563,9 +566,12 @@ fn test_removed_modules_can_not_execute() { #[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 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: CREATOR_ADDR.to_string(), @@ -596,78 +602,53 @@ fn test_module_already_disabled() { 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(); - + 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_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(), + 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: vec![ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), 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(); + label: "governance module".to_string(), + }], + to_disable, + }) + .unwrap(), + } + .into()]) + .unwrap_err(); - assert_eq!( - err, - ContractError::ModuleAlreadyDisabled { + assert!(format!("{:?}", err).contains( + &ContractError::ModuleAlreadyDisabled { address: start_module.address } - ) + .to_string() + )); } #[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 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: CREATOR_ADDR.to_string(), @@ -698,82 +679,48 @@ fn test_swap_voting_module() { 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(); - + gov.instantiate(&gov_instantiate, None, None).unwrap(); + let modules = gov.proposal_modules(None, None).unwrap(); assert_eq!(modules.len(), 1); + gov_mod.set_address(&modules[0].address); - 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 voting_addr = gov.voting_module().unwrap(); - let new_voting_addr: Addr = app - .wrap() - .query_wasm_smart(gov_addr, &QueryMsg::VotingModule {}) + gov_mod + .proposal_execute(vec![WasmMsg::Execute { + contract_addr: gov.address().unwrap().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(); - assert_ne!(new_voting_addr, voting_addr); + assert_ne!(gov.voting_module().unwrap(), 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(); +fn test_unauthorized(gov: &DaoDaoCore, msg: ExecuteMsg) { + let err = gov.execute(&msg, None).unwrap_err(); - assert_eq!(err, ContractError::Unauthorized {}); + assert!(format!("{:?}", err).contains(&ContractError::Unauthorized {}.to_string())); } #[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 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: CREATOR_ADDR.to_string(), @@ -804,20 +751,10 @@ fn test_permissions() { automatically_add_cw721s: true, }; - let gov_addr = app - .instantiate_contract( - gov_id, - Addr::unchecked(CREATOR_ADDR), - &gov_instantiate, - &[], - "cw-governance", - None, - ) - .unwrap(); + gov.instantiate(&gov_instantiate, None, None).unwrap(); test_unauthorized( - &mut app, - gov_addr.clone(), + &gov, ExecuteMsg::UpdateVotingModule { module: ModuleInstantiateInfo { code_id: govmod_id, @@ -830,8 +767,7 @@ fn test_permissions() { ); test_unauthorized( - &mut app, - gov_addr.clone(), + &gov, ExecuteMsg::UpdateProposalModules { to_add: vec![], to_disable: vec![], @@ -839,8 +775,7 @@ fn test_permissions() { ); test_unauthorized( - &mut app, - gov_addr, + &gov, ExecuteMsg::UpdateConfig { config: Config { dao_uri: None, @@ -854,19 +789,26 @@ fn test_permissions() { ); } -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()); +fn do_standard_instantiate( + auto_add: bool, + admin: Option, +) -> ( + DaoDaoCore, + DaoProposalSudo, + MockBech32, +) { + let mock = MockBech32::new("mock"); + let gov_mod = DaoProposalSudo::new("proposal", mock.clone()); + let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); + let gov = DaoDaoCore::new("dao-core", mock.clone()); + let cw20 = Cw20Base::new("cw20", mock.clone()); let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { - root: CREATOR_ADDR.to_string(), + root: mock.sender().to_string(), }; let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { token_info: dao_voting_cw20_balance::msg::TokenInfo::New { - code_id: cw20_id, + code_id: cw20.code_id().unwrap(), label: "DAO DAO voting".to_string(), name: "DAO DAO".to_string(), symbol: "DAO".to_string(), @@ -888,14 +830,14 @@ fn do_standard_instantiate(auto_add: bool, admin: Option) -> (Addr, App) automatically_add_cw20s: auto_add, automatically_add_cw721s: auto_add, voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: voting_id, + code_id: voting.code_id().unwrap(), 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, + code_id: gov_mod.code_id().unwrap(), msg: to_json_binary(&govmod_instantiate).unwrap(), admin: Some(Admin::CoreModule {}), funds: vec![], @@ -904,212 +846,143 @@ fn do_standard_instantiate(auto_add: bool, admin: Option) -> (Addr, App) initial_items: None, }; - let gov_addr = app - .instantiate_contract( - gov_id, - Addr::unchecked(CREATOR_ADDR), - &gov_instantiate, - &[], - "cw-governance", - None, - ) - .unwrap(); + gov.instantiate(&gov_instantiate, None, None).unwrap(); - (gov_addr, app) + let proposal_modules = gov.proposal_modules(None, None).unwrap(); + assert_eq!(proposal_modules.len(), 1); + let proposal_module = proposal_modules.into_iter().next().unwrap(); + gov_mod.set_address(&proposal_module.address); + (gov, gov_mod, mock) } #[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(); + let (core, proposal, mock) = do_standard_instantiate(true, None); - assert_eq!(proposal_modules.len(), 1); - let proposal_module = proposal_modules.into_iter().next().unwrap(); + let random = mock.addr_make("random"); + let start_height = mock.block_info().unwrap().height; // 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(); + core.call_as(&random) + .execute_admin_msgs(vec![WasmMsg::Execute { + contract_addr: core.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .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(); + core.call_as(&proposal.address().unwrap()) + .execute_admin_msgs(vec![WasmMsg::Execute { + contract_addr: core.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .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(); + core.call_as(&random) + .nominate_admin(Some(random.to_string())) + .unwrap(); // 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(); + + core.call_as(&proposal.address().unwrap()) + .execute_proposal_hook(vec![WasmMsg::Execute { + contract_addr: core.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .unwrap(); // Instantiate new DAO with an admin - let (core_with_admin_addr, mut app) = - do_standard_instantiate(true, Some(Addr::unchecked("admin").to_string())); + let admin = mock.addr_make("admin"); + let (core_with_admin, proposal_with_admin_address, mock) = + do_standard_instantiate(true, Some(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(); + core_with_admin + .call_as(&proposal_with_admin_address.address().unwrap()) + .execute_admin_msgs(vec![WasmMsg::Execute { + contract_addr: core_with_admin.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .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()); + core_with_admin + .call_as(&admin) + .pause(Duration::Height(10)) + .unwrap_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()); + core_with_admin + .call_as(&random) + .pause(Duration::Height(10)) + .unwrap_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()); + let res = core_with_admin + .call_as(&admin) + .execute_admin_msgs(vec![WasmMsg::Execute { + contract_addr: core_with_admin.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .unwrap(); // 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, + core_with_admin.pause_info().unwrap(), PauseInfoResponse::Paused { expiration: Expiration::AtHeight(start_height + 10) } ); // DAO unpauses after 10 blocks - app.update_block(|block| block.height += 11); + mock.wait_blocks(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 {}); + assert_eq!( + core_with_admin.pause_info().unwrap(), + 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()); + let res = core_with_admin + .call_as(&admin) + .execute_admin_msgs(vec![WasmMsg::Execute { + contract_addr: core_with_admin.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .unwrap(); // DAO with admin cannot unpause itself let res = app.execute_contract( @@ -2836,9 +2709,12 @@ fn test_execute_stargate_msg() { #[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 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: CREATOR_ADDR.to_string(), @@ -2885,28 +2761,9 @@ fn test_module_prefixes() { 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(); + let gov_addr = gov.instantiate(&gov_instantiate, None, None).unwrap(); + let modules = gov.proposal_modules(None, None).unwrap(); assert_eq!(modules.len(), 3); let module_1 = &modules[0]; @@ -2939,8 +2796,7 @@ fn test_add_remove_subdaos() { let (core_addr, mut app) = do_standard_instantiate(false, None); test_unauthorized( - &mut app, - core_addr.clone(), + &gov, ExecuteMsg::UpdateSubDaos { to_add: vec![], to_remove: vec![], From 9492e370678d9710cf32b802ddb4c8c614b91961 Mon Sep 17 00:00:00 2001 From: Kayanski Date: Fri, 17 May 2024 07:40:35 +0000 Subject: [PATCH 3/8] More tests --- Cargo.lock | 5 + contracts/dao-dao-core/Cargo.toml | 2 + contracts/dao-dao-core/src/cw_orch_tests.rs | 2597 ++++++++---------- packages/cw-orch/Cargo.toml | 3 + packages/cw-orch/src/test_contracts/cw721.rs | 20 + packages/cw-orch/src/test_contracts/mod.rs | 2 + packages/dao-interface/src/msg.rs | 4 +- 7 files changed, 1141 insertions(+), 1492 deletions(-) create mode 100644 packages/cw-orch/src/test_contracts/cw721.rs diff --git a/Cargo.lock b/Cargo.lock index f92677367..35078a4e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1977,10 +1977,12 @@ dependencies = [ name = "dao-cw-orch" version = "2.4.2" dependencies = [ + "cosmwasm-std", "cw-orch", "cw20-stake 2.4.2", "cw20-stake-external-rewards", "cw20-stake-reward-distributor", + "cw721-base 0.18.0", "dao-dao-core", "dao-interface", "dao-pre-propose-approval-single", @@ -1999,6 +2001,7 @@ dependencies = [ "dao-voting-cw721-roles", "dao-voting-cw721-staked", "dao-voting-token-staked", + "serde", ] [[package]] @@ -2016,6 +2019,8 @@ name = "dao-dao-core" version = "2.4.2" dependencies = [ "abstract-cw-plus-interface", + "abstract-cw20", + "abstract-cw20-base", "cosmwasm-schema", "cosmwasm-std", "cw-core", diff --git a/contracts/dao-dao-core/Cargo.toml b/contracts/dao-dao-core/Cargo.toml index 18c3da829..eee9f8938 100644 --- a/contracts/dao-dao-core/Cargo.toml +++ b/contracts/dao-dao-core/Cargo.toml @@ -32,6 +32,8 @@ cw-core-v1 = { workspace = true, features = ["library"] } [dev-dependencies] abstract-cw-plus-interface = "2.0.1" +abstract-cw20 = "2.0.0" +abstract-cw20-base = "2.0.0" cw-multi-test = { workspace = true, features = ["stargate"] } cw-orch = "0.22.2" cw20-base = { workspace = true } diff --git a/contracts/dao-dao-core/src/cw_orch_tests.rs b/contracts/dao-dao-core/src/cw_orch_tests.rs index 1bc816c65..340a91851 100644 --- a/contracts/dao-dao-core/src/cw_orch_tests.rs +++ b/contracts/dao-dao-core/src/cw_orch_tests.rs @@ -3,18 +3,20 @@ use crate::{ state::PROPOSAL_MODULES, ContractError, }; +use abstract_cw20::msg::Cw20ExecuteMsgFns; 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, + to_json_binary, Addr, CosmosMsg, Empty, StdError, 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::Cw721Base; use dao_cw_orch::{DaoDaoCore, DaoProposalSudo, DaoVotingCw20Balance}; use dao_interface::CoreExecuteMsgFns; use dao_interface::CoreQueryMsgFns; @@ -28,7 +30,10 @@ use dao_interface::{ voting::{InfoResponse, VotingPowerAtHeightResponse}, }; use dao_proposal_sudo::msg::ExecuteMsgFns as _; -const CREATOR_ADDR: &str = "creator"; + +pub fn assert_contains(e: impl std::fmt::Debug, el: impl ToString) { + assert!(format!("{:?}", e).contains(&el.to_string())) +} fn cw20_contract() -> Box> { let contract = ContractWrapper::new( @@ -363,6 +368,8 @@ fn test_swap_governance(swaps: Vec<(u32, u32)>) { let start_modules = gov.proposal_modules(None, None).unwrap(); let start_modules_active: Vec = get_active_modules(&gov); + + get_active_modules(&gov); gov_mod.set_address(&start_modules_active[0].address.clone()); let to_add: Vec<_> = (0..add) .map(|n| ModuleInstantiateInfo { @@ -540,12 +547,12 @@ fn test_removed_modules_can_not_execute() { .into()]) .unwrap_err(); - assert!(format!("{:?}", err).contains( - &ContractError::ModuleDisabledCannotExecute { - address: Addr::unchecked("") - } - .to_string() - )); + assert_contains( + err, + ContractError::ModuleDisabledCannotExecute { + address: Addr::unchecked(""), + }, + ); // Check that the enabled query works. let enabled_modules = gov.active_proposal_modules(None, None).unwrap(); @@ -574,7 +581,7 @@ fn test_module_already_disabled() { gov.upload().unwrap(); let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { - root: CREATOR_ADDR.to_string(), + root: mock.sender().to_string(), }; let gov_instantiate = InstantiateMsg { @@ -633,12 +640,12 @@ fn test_module_already_disabled() { .into()]) .unwrap_err(); - assert!(format!("{:?}", err).contains( - &ContractError::ModuleAlreadyDisabled { - address: start_module.address - } - .to_string() - )); + assert_contains( + err, + ContractError::ModuleAlreadyDisabled { + address: start_module.address, + }, + ); } #[test] @@ -651,7 +658,7 @@ fn test_swap_voting_module() { gov.upload().unwrap(); let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { - root: CREATOR_ADDR.to_string(), + root: mock.sender().to_string(), }; let gov_instantiate = InstantiateMsg { @@ -710,7 +717,7 @@ fn test_swap_voting_module() { fn test_unauthorized(gov: &DaoDaoCore, msg: ExecuteMsg) { let err = gov.execute(&msg, None).unwrap_err(); - assert!(format!("{:?}", err).contains(&ContractError::Unauthorized {}.to_string())); + assert_contains(err, ContractError::Unauthorized {}); } #[test] @@ -723,7 +730,7 @@ fn test_permissions() { gov.upload().unwrap(); let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { - root: CREATOR_ADDR.to_string(), + root: mock.sender().to_string(), }; let gov_instantiate = InstantiateMsg { @@ -791,18 +798,24 @@ fn test_permissions() { fn do_standard_instantiate( auto_add: bool, - admin: Option, + admin: bool, ) -> ( DaoDaoCore, DaoProposalSudo, MockBech32, + Option, ) { let mock = MockBech32::new("mock"); let gov_mod = DaoProposalSudo::new("proposal", mock.clone()); let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); - let gov = DaoDaoCore::new("dao-core", mock.clone()); + let mut gov = DaoDaoCore::new("dao-core", mock.clone()); let cw20 = Cw20Base::new("cw20", mock.clone()); + gov_mod.upload().unwrap(); + voting.upload().unwrap(); + gov.upload().unwrap(); + cw20.upload().unwrap(); + let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { root: mock.sender().to_string(), }; @@ -814,16 +827,17 @@ fn do_standard_instantiate( symbol: "DAO".to_string(), decimals: 6, initial_balances: vec![cw20::Cw20Coin { - address: CREATOR_ADDR.to_string(), + address: mock.sender().to_string(), amount: Uint128::from(2u64), }], marketing: None, }, }; + let admin = admin.then(|| mock.addr_make("admin")); let gov_instantiate = InstantiateMsg { dao_uri: None, - admin, + admin: admin.as_ref().map(|a| a.to_string()), name: "DAO DAO".to_string(), description: "A DAO that builds DAOs.".to_string(), image_url: None, @@ -852,12 +866,17 @@ fn do_standard_instantiate( assert_eq!(proposal_modules.len(), 1); let proposal_module = proposal_modules.into_iter().next().unwrap(); gov_mod.set_address(&proposal_module.address); - (gov, gov_mod, mock) + + if admin.is_none() { + gov = gov.call_as(&gov.address().unwrap()); + } + + (gov, gov_mod, mock, admin) } #[test] fn test_admin_permissions() { - let (core, proposal, mock) = do_standard_instantiate(true, None); + let (core, proposal, mock, _) = do_standard_instantiate(true, false); let random = mock.addr_make("random"); let start_height = mock.block_info().unwrap().height; @@ -891,7 +910,7 @@ fn test_admin_permissions() { // Update Admin can't be called by non-admins core.call_as(&random) .nominate_admin(Some(random.to_string())) - .unwrap(); + .unwrap_err(); // Nominate admin can be called by core contract as no admin was // specified so the admin defaulted to the core contract. @@ -909,9 +928,9 @@ fn test_admin_permissions() { .unwrap(); // Instantiate new DAO with an admin - let admin = mock.addr_make("admin"); - let (core_with_admin, proposal_with_admin_address, mock) = - do_standard_instantiate(true, Some(admin.to_string())); + let (core_with_admin, proposal_with_admin_address, mock, admin) = + do_standard_instantiate(true, true); + let admin = admin.unwrap(); // Non admins still can't call ExecuteAdminMsgs core_with_admin @@ -962,7 +981,7 @@ fn test_admin_permissions() { ); // DAO unpauses after 10 blocks - mock.wait_blocks(11); + mock.wait_blocks(11).unwrap(); // Check we are unpaused assert_eq!( @@ -985,350 +1004,201 @@ fn test_admin_permissions() { .unwrap(); // 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()); + let res = core_with_admin + .call_as(&core_with_admin.address().unwrap()) + .unpause() + .unwrap_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()); + let res = core_with_admin.call_as(&random).unpause().unwrap_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()); + let res = core_with_admin.call_as(&admin).unpause().unwrap(); // 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()), - }, - &[], + assert_eq!( + core_with_admin.pause_info().unwrap(), + PauseInfoResponse::Unpaused {} ); - res.unwrap(); - let nomination: AdminNominationResponse = app - .wrap() - .query_wasm_smart(core_with_admin_addr.clone(), &QueryMsg::AdminNomination {}) + // Admin can nominate a new admin. + let new_admin = mock.addr_make("meow"); + core_with_admin + .call_as(&admin) + .nominate_admin(Some(new_admin.to_string())) .unwrap(); + assert_eq!( - nomination, + core_with_admin.admin_nomination().unwrap(), AdminNominationResponse { - nomination: Some(Addr::unchecked("meow")) + nomination: Some(new_admin.clone()) } ); // 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")); + assert_eq!(core_with_admin.admin().unwrap(), 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 {}); + let err = core_with_admin + .call_as(&random) + .accept_admin_nomination() + .unwrap_err(); + + assert_contains(err, ContractError::Unauthorized {}); // Accept the nomination. - app.execute_contract( - Addr::unchecked("meow"), - core_with_admin_addr.clone(), - &ExecuteMsg::AcceptAdminNomination {}, - &[], - ) - .unwrap(); + core_with_admin + .call_as(&new_admin) + .accept_admin_nomination() + .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")); + assert_eq!(core_with_admin.admin().unwrap(), new_admin); // 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 }); + assert_eq!( + core_with_admin.admin_nomination().unwrap(), + AdminNominationResponse { nomination: None } + ); } #[test] fn test_admin_nomination() { - let (core_addr, mut app) = do_standard_instantiate(true, Some("admin".to_string())); + let (core, _, mock, admin) = do_standard_instantiate(true, true); + let admin = admin.unwrap(); // 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 }); + assert_eq!( + core.admin_nomination().unwrap(), + AdminNominationResponse { nomination: None } + ); // Nominate a new admin. - app.execute_contract( - Addr::unchecked("admin"), - core_addr.clone(), - &ExecuteMsg::NominateAdmin { - admin: Some("ekez".to_string()), - }, - &[], - ) - .unwrap(); + let ekez = mock.addr_make("ekez"); + core.call_as(&admin) + .nominate_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, + core.admin_nomination().unwrap(), AdminNominationResponse { - nomination: Some(Addr::unchecked("ekez")) + nomination: Some(ekez.clone()) } ); // 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 {}); + let err = core.call_as(&ekez).withdraw_admin_nomination().unwrap_err(); + assert_contains(err, ContractError::Unauthorized {}); // Admin can withdraw. - app.execute_contract( - Addr::unchecked("admin"), - core_addr.clone(), - &ExecuteMsg::WithdrawAdminNomination {}, - &[], - ) - .unwrap(); + core.call_as(&admin).withdraw_admin_nomination().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 }); + assert_eq!( + core.admin_nomination().unwrap(), + 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 {}); + let err = core + .call_as(&admin) + .withdraw_admin_nomination() + .unwrap_err(); + + assert_contains(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 {}); + let err = core.call_as(&admin).accept_admin_nomination().unwrap_err(); + + assert_contains(err, ContractError::NoAdminNomination {}); // Nominate a new admin. - app.execute_contract( - Addr::unchecked("admin"), - core_addr.clone(), - &ExecuteMsg::NominateAdmin { - admin: Some("meow".to_string()), - }, - &[], - ) - .unwrap(); + let meow = mock.addr_make("meow"); + core.call_as(&admin) + .nominate_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 {}); + let err = core + .call_as(&admin) + .nominate_admin(Some(ekez.to_string())) + .unwrap_err(); + assert_contains(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 {}); + let err = core.call_as(&ekez).accept_admin_nomination().unwrap_err(); + assert_contains(err, ContractError::Unauthorized {}); - app.execute_contract( - Addr::unchecked("meow"), - core_addr.clone(), - &ExecuteMsg::AcceptAdminNomination {}, - &[], - ) - .unwrap(); + core.call_as(&meow).accept_admin_nomination().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())); + assert_eq!(core.admin().unwrap(), meow); - let start_height = app.block_info().height; + let start_height = mock.block_info().unwrap().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 err = core + .call_as(&admin) + .execute_admin_msgs(vec![WasmMsg::Execute { + contract_addr: core.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .unwrap_err(); + assert_contains(err, ContractError::Unauthorized {}); - let paused: PauseInfoResponse = app - .wrap() - .query_wasm_smart(core_addr.clone(), &QueryMsg::PauseInfo {}) + core.call_as(&meow) + .execute_admin_msgs(vec![WasmMsg::Execute { + contract_addr: core.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) .unwrap(); + assert_eq!( - paused, + core.pause_info().unwrap(), PauseInfoResponse::Paused { expiration: Expiration::AtHeight(start_height + 10) } ); // DAO unpauses after 10 blocks - app.update_block(|block| block.height += 11); + mock.wait_blocks(11).unwrap(); // Remove the admin. - app.execute_contract( - Addr::unchecked("meow"), - core_addr.clone(), - &ExecuteMsg::NominateAdmin { admin: None }, - &[], - ) - .unwrap(); + core.call_as(&meow).nominate_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 }); + assert_eq!( + core.admin_nomination().unwrap(), + 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); + assert_eq!(core.admin().unwrap(), core.address().unwrap()); } #[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(); + let (gov, _, mock, _) = do_standard_instantiate(true, false); assert_eq!( - creator_voting_power, + gov.voting_power_at_height(mock.sender().to_string(), None) + .unwrap(), VotingPowerAtHeightResponse { power: Uint128::from(2u64), - height: app.block_info().height, + height: mock.block_info().unwrap().height, } ); } @@ -1353,77 +1223,31 @@ fn remove_item(app: &mut App, gov_addr: Addr, key: String) { .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 {}); + let (gov, _, mock, _) = do_standard_instantiate(true, false); + + let ekez = mock.addr_make("ekez"); + let err = gov + .call_as(&ekez) + .set_item("k".to_string(), "v".to_string()) + .unwrap_err(); + assert_contains(err, ContractError::Unauthorized {}); + + let err = gov.call_as(&ekez).remove_item("k".to_string()).unwrap_err(); + assert_contains(err, ContractError::Unauthorized {}); } #[test] fn test_add_remove_get() { - let (gov_addr, mut app) = do_standard_instantiate(true, None); + let (gov, _, mock, _) = do_standard_instantiate(true, false); - let a = get_item(&mut app, gov_addr.clone(), "aaaaa".to_string()); + let a = gov.get_item("aaaaa".to_string()).unwrap(); 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()); + gov.set_item("aaaaakey".to_string(), "aaaaaaddr".to_string()) + .unwrap(); + let a = gov.get_item("aaaaakey".to_string()).unwrap(); assert_eq!( a, GetItemResponse { @@ -1431,38 +1255,42 @@ fn test_add_remove_get() { } ); - remove_item(&mut app, gov_addr.clone(), "aaaaakey".to_string()); - let a = get_item(&mut app, gov_addr, "aaaaakey".to_string()); + gov.remove_item("aaaaakey".to_string()).unwrap(); + let a = gov.get_item("aaaaakey".to_string()).unwrap(); 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()) + let (gov, _, _, _) = do_standard_instantiate(true, false); + gov.remove_item("b".to_string()).unwrap(); } #[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 mock = MockBech32::new("mock"); + let govmod = DaoProposalSudo::new("proposal", mock.clone()); + let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); + let gov = DaoDaoCore::new("dao-core", mock.clone()); + let cw20 = Cw20Base::new("cw20", mock.clone()); + govmod.upload().unwrap(); + voting.upload().unwrap(); + gov.upload().unwrap(); + cw20.upload().unwrap(); let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { - root: CREATOR_ADDR.to_string(), + root: mock.sender().to_string(), }; let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { token_info: dao_voting_cw20_balance::msg::TokenInfo::New { - code_id: cw20_id, + code_id: cw20.code_id().unwrap(), 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(), + address: mock.sender().to_string(), amount: Uint128::from(2u64), }], marketing: None, @@ -1478,14 +1306,14 @@ fn test_list_items() { automatically_add_cw20s: true, automatically_add_cw721s: true, voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: voting_id, + code_id: voting.code_id().unwrap(), 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, + code_id: govmod.code_id().unwrap(), msg: to_json_binary(&govmod_instantiate).unwrap(), admin: Some(Admin::CoreModule {}), funds: vec![], @@ -1494,64 +1322,41 @@ fn test_list_items() { initial_items: None, }; - let gov_addr = app - .instantiate_contract( - gov_id, - Addr::unchecked(CREATOR_ADDR), - &gov_instantiate, - &[], - "cw-governance", - None, - ) - .unwrap(); + gov.instantiate(&gov_instantiate, None, None).unwrap(); + let gov = gov.call_as(&gov.address().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(), - ); + gov.set_item("fookey".to_string(), "fooaddr".to_string()) + .unwrap(); + gov.set_item("barkey".to_string(), "baraddr".to_string()) + .unwrap(); + gov.set_item("loremkey".to_string(), "loremaddr".to_string()) + .unwrap(); + gov.set_item("ipsumkey".to_string(), "ipsumaddr".to_string()) + .unwrap(); // 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)); + let first_item = gov.list_items(Some(1), None).unwrap(); 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)); + let no_items = gov.list_items(Some(0), None).unwrap(); 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); + let last_item = gov.list_items(None, Some("foo".to_string())).unwrap(); + 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)); + let after_foo_list = gov.list_items(Some(4), Some("ipsum".to_string())).unwrap(); assert_eq!(after_foo_list.len(), 2); assert_eq!( after_foo_list, @@ -1564,24 +1369,29 @@ fn test_list_items() { #[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 mock = MockBech32::new("mock"); + let govmod = DaoProposalSudo::new("proposal", mock.clone()); + let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); + let gov = DaoDaoCore::new("dao-core", mock.clone()); + let cw20 = Cw20Base::new("cw20", mock.clone()); + + govmod.upload().unwrap(); + voting.upload().unwrap(); + gov.upload().unwrap(); + cw20.upload().unwrap(); let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { - root: CREATOR_ADDR.to_string(), + root: mock.sender().to_string(), }; let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { token_info: dao_voting_cw20_balance::msg::TokenInfo::New { - code_id: cw20_id, + code_id: cw20.code_id().unwrap(), 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(), + address: mock.sender().to_string(), amount: Uint128::from(2u64), }], marketing: None, @@ -1612,14 +1422,14 @@ fn test_instantiate_with_items() { automatically_add_cw20s: true, automatically_add_cw721s: true, voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: voting_id, + code_id: voting.code_id().unwrap(), 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, + code_id: govmod.code_id().unwrap(), msg: to_json_binary(&govmod_instantiate).unwrap(), admin: Some(Admin::CoreModule {}), funds: vec![], @@ -1629,45 +1439,26 @@ fn test_instantiate_with_items() { }; // 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!( + let err = gov.instantiate(&gov_instantiate, None, None).unwrap_err(); + assert_contains( err, ContractError::DuplicateInitialItem { - item: "item0".to_string() - } + 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(); + let gov_addr = gov.instantiate(&gov_instantiate, None, None).unwrap(); // Ensure initial items were added. - let items = list_items(&mut app, gov_addr.clone(), None, None); + let items = gov.list_items(None, None).unwrap(); 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()); + let get_item0 = gov.get_item("item0".to_string()).unwrap(); + assert_eq!( get_item0, GetItemResponse { @@ -1676,20 +1467,18 @@ fn test_instantiate_with_items() { ); assert_eq!(items[0].0, "item1".to_string()); - let item1_value = get_item(&mut app, gov_addr, "item1".to_string()).item; + let item1_value = gov.get_item("item1".to_string()).unwrap().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 { + let (gov, proposal, mock, _) = do_standard_instantiate(true, false); + let another_cw20 = Cw20Base::new("another-cw20", mock.clone()); + another_cw20.upload().unwrap(); + another_cw20 + .instantiate( + &abstract_cw20_base::msg::InstantiateMsg { name: "DAO".to_string(), symbol: "DAO".to_string(), decimals: 6, @@ -1697,146 +1486,85 @@ fn test_cw20_receive_auto_add() { mint: None, marketing: None, }, - &[], - "another-token", + None, 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(); + let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); + voting.set_address(&gov.voting_module().unwrap()); + let gov_token = Cw20Base::new("cw20", mock.clone()); + gov_token.set_address( + &voting + .query(&dao_voting_cw20_balance::msg::QueryMsg::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(); + let cw20_balances = gov.cw_20_balances(None, 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, - }, + gov_token + .send( + Uint128::new(1), + gov.address().unwrap().to_string(), + to_json_binary(&"").unwrap(), ) .unwrap(); + + let cw20_list = gov.cw_20_token_list(None, None).unwrap(); + assert_eq!( + cw20_list, + vec![gov_token.address().unwrap().to_string().clone()] + ); + assert_eq!( - cw20_balances, + gov.cw_20_balances(None, None).unwrap(), vec![Cw20BalanceResponse { - addr: gov_token.clone(), + addr: gov_token.address().unwrap(), 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()], - }, - &[], + let err = gov + .update_cw_20_list( + vec![mock.addr_make("new").to_string()], + vec![gov_token.address().unwrap().to_string()], ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!(err, ContractError::Std(_))); + .unwrap_err(); + println!("{:?}", err); + assert_contains(&err, "key:"); + assert_contains(err, "not found"); // 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 {})); + let err = gov + .call_as(&mock.addr_make("ekez")) + .update_cw_20_list(vec![], vec![gov_token.address().unwrap().to_string()]) + .unwrap_err(); - 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()], - }, - &[], + assert_contains(err, ContractError::Unauthorized {}); + + gov.update_cw_20_list( + vec![another_cw20.address().unwrap().to_string()], + vec![gov_token.address().unwrap().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]); + let cw20_list = gov.cw_20_token_list(None, None).unwrap(); + assert_eq!(cw20_list, vec![another_cw20.address().unwrap().to_string()]); } #[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 { + let (gov, proposal, mock, _) = do_standard_instantiate(false, false); + + let another_cw20 = Cw20Base::new("another-cw20", mock.clone()); + another_cw20.upload().unwrap(); + another_cw20 + .instantiate( + &abstract_cw20_base::msg::InstantiateMsg { name: "DAO".to_string(), symbol: "DAO".to_string(), decimals: 6, @@ -1844,943 +1572,879 @@ fn test_cw20_receive_no_auto_add() { mint: None, marketing: None, }, - &[], - "another-token", + None, 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(); + let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); + voting.set_address(&gov.voting_module().unwrap()); + + let gov_token = Cw20Base::new("cw20", mock.clone()); + gov_token.set_address( + &voting + .query(&dao_voting_cw20_balance::msg::QueryMsg::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, - }, + gov_token + .send( + Uint128::new(1), + gov.address().unwrap().to_string(), + to_json_binary(&"").unwrap(), ) .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()], - }, - &[], + assert_eq!( + gov.cw_20_token_list(None, None).unwrap(), + Vec::::new() + ); + + gov.update_cw_20_list( + vec![ + another_cw20.address().unwrap().to_string(), + gov_token.address().unwrap().to_string(), + ], + vec![mock.addr_make("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]); + assert_eq!( + gov.cw_20_token_list(None, None).unwrap(), + vec![ + gov_token.address().unwrap(), + another_cw20.address().unwrap(), + ] + ); } #[test] fn test_cw721_receive() { - let (gov_addr, mut app) = do_standard_instantiate(true, None); - - let cw721_id = app.store_code(cw721_contract()); + let (gov, proposal, mock, _) = do_standard_instantiate(true, false); - let cw721_addr = app - .instantiate_contract( - cw721_id, - Addr::unchecked(CREATOR_ADDR), + let cw721 = Cw721Base::new("cw721", mock.clone()); + cw721.upload().unwrap(); + cw721 + .instantiate( &cw721_base::msg::InstantiateMsg { name: "ekez".to_string(), symbol: "ekez".to_string(), - minter: CREATOR_ADDR.to_string(), + minter: mock.sender().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), + let another_cw721 = Cw721Base::new("another_cw721", mock.clone()); + another_cw721.set_code_id(cw721.code_id().unwrap()); + another_cw721 + .instantiate( &cw721_base::msg::InstantiateMsg { name: "ekez".to_string(), symbol: "ekez".to_string(), - minter: CREATOR_ADDR.to_string(), + minter: mock.sender().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, + cw721 + .execute( + &cw721_base::msg::ExecuteMsg::, Empty>::Mint { + token_id: "ekez".to_string(), + owner: mock.sender().to_string(), + token_uri: None, + extension: None, }, + 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), + cw721 + .execute( + &cw721_base::msg::ExecuteMsg::, Empty>::SendNft { + contract: gov.address().unwrap().to_string(), + token_id: "ekez".to_string(), + msg: to_json_binary("").unwrap(), }, - &[], + None, ) - .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, - }, - }, - &[], + gov.cw_721_token_list(None, None).unwrap(), + vec![cw721.address().unwrap().clone()] ); - 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()], - }, - &[], + // Try to add an invalid cw721. + let err = gov + .update_cw_721_list( + vec![ + mock.addr_make("new").to_string(), + cw721.address().unwrap().clone().to_string(), + ], + vec![cw721.address().unwrap().clone().to_string()], ) - .unwrap_err() - .downcast() - .unwrap(); + .unwrap_err(); - assert!(matches!(err, ContractError::Paused { .. })); + // TODO + // assert!(matches!(err, ContractError::Std(_))); - app.update_block(|block| block.height += 1); + // Test that non-DAO can not update the list. + let err = gov + .call_as(&mock.addr_make("ekez")) + .update_cw_721_list(vec![], vec![cw721.address().unwrap().clone().to_string()]) + .unwrap_err(); - 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 {}); + assert_contains(err, ContractError::Unauthorized {}); - // 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()], - }, - &[], + // Add a real cw721. + gov.update_cw_721_list( + vec![ + cw721.address().unwrap().to_string(), + another_cw721.address().unwrap().to_string(), + ], + vec![cw721.address().unwrap().to_string()], ) .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) - } + gov.cw_721_token_list(None, None).unwrap(), + vec![another_cw721.address().unwrap()] ); } +// #[test] +// fn test_cw721_receive_no_auto_add() { +// let (gov, proposal, mock, _) = do_standard_instantiate(false, None); + +// let cw721_id = app.store_code(cw721_contract()); + +// let cw721_addr = app +// .instantiate_contract( +// cw721_id, +// Addr::unchecked(mock.sender()), +// &cw721_base::msg::InstantiateMsg { +// name: "ekez".to_string(), +// symbol: "ekez".to_string(), +// minter: mock.sender().to_string(), +// }, +// &[], +// "cw721", +// None, +// ) +// .unwrap(); + +// let another_cw721 = app +// .instantiate_contract( +// cw721_id, +// Addr::unchecked(mock.sender()), +// &cw721_base::msg::InstantiateMsg { +// name: "ekez".to_string(), +// symbol: "ekez".to_string(), +// minter: mock.sender().to_string(), +// }, +// &[], +// "cw721", +// None, +// ) +// .unwrap(); + +// app.execute_contract( +// Addr::unchecked(mock.sender()), +// cw721_addr.clone(), +// &cw721_base::msg::ExecuteMsg::, Empty>::Mint { +// token_id: "ekez".to_string(), +// owner: mock.sender().to_string(), +// token_uri: None, +// extension: None, +// }, +// &[], +// ) +// .unwrap(); + +// app.execute_contract( +// Addr::unchecked(mock.sender()), +// 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 (gov, proposal, mock, _) = 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(); + let (gov, proposal, mock, _) = do_standard_instantiate(false, false); + let proposal_modules = gov.proposal_modules(None, 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(); + let all_state: DumpStateResponse = gov.dump_state().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 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: 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 = gov.instantiate(&gov_instantiate, None, None).unwrap(); - - let modules = gov.proposal_modules(None, 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); -} +// // 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: mock.sender().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: mock.sender().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(mock.sender()), +// &gov_instantiate, +// &[], +// "cw-governance", +// Some(mock.sender().to_string()), +// ) +// .unwrap(); + +// let state: DumpStateResponse = app +// .wrap() +// .query_wasm_smart(core_addr.clone(), &QueryMsg::DumpState {}) +// .unwrap(); + +// app.execute( +// Addr::unchecked(mock.sender()), +// 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: mock.sender().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: mock.sender().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(mock.sender()), +// &v1_core_instantiate, +// &[], +// "cw-governance", +// Some(mock.sender().to_string()), +// ) +// .unwrap(); + +// app.execute( +// Addr::unchecked(mock.sender()), +// 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(mock.sender()), +// 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 (gov, proposal, mock, _) = 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 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: "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 = gov.instantiate(&gov_instantiate, None, None).unwrap(); + +// let modules = gov.proposal_modules(None, 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(); @@ -2793,10 +2457,10 @@ fn get_active_modules(gov: &DaoDaoCore) -> Vec = vec![ SubDao { - addr: "subdao001".to_string(), + addr: mock.addr_make("subdao001").to_string(), charter: None, }, SubDao { - addr: "subdao002".to_string(), + addr: mock.addr_make("subdao002").to_string(), charter: Some("cool charter bro".to_string()), }, SubDao { - addr: "subdao005".to_string(), + addr: mock.addr_make("subdao005").to_string(), charter: None, }, SubDao { - addr: "subdao007".to_string(), + addr: mock.addr_make("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(); + gov.update_sub_daos(to_add, to_remove).unwrap(); - assert_eq!(res.len(), 4); + assert_eq!(gov.list_sub_daos(None, None).unwrap().len(), 4); - let to_remove: Vec = vec!["subdao005".to_string()]; + let to_remove: Vec = vec![mock.addr_make("subdao005").to_string()]; - app.execute_contract( - Addr::unchecked(core_addr.clone()), - core_addr.clone(), - &ExecuteMsg::UpdateSubDaos { - to_add: vec![], - to_remove, - }, - &[], - ) - .unwrap(); + gov.update_sub_daos(vec![], to_remove).unwrap(); - let res: Vec = app - .wrap() - .query_wasm_smart( - core_addr, - &QueryMsg::ListSubDaos { - start_after: None, - limit: None, - }, - ) - .unwrap(); + let res = gov.list_sub_daos(None, 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(), + addr: mock.addr_make("subdao001").to_string(), charter: None, }, SubDao { - addr: "subdao002".to_string(), + addr: mock.addr_make("subdao002").to_string(), charter: Some("cool charter bro".to_string()), }, SubDao { - addr: "subdao007".to_string(), + addr: mock.addr_make("subdao007").to_string(), charter: None, }, ]; @@ -2907,13 +2528,9 @@ pub fn test_migrate_update_version() { #[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(); + let (gov, _, _, _) = do_standard_instantiate(true, false); assert_eq!( - res, + gov.info().unwrap(), InfoResponse { info: ContractVersion { contract: CONTRACT_NAME.to_string(), diff --git a/packages/cw-orch/Cargo.toml b/packages/cw-orch/Cargo.toml index 8c27bfdde..5b637879e 100644 --- a/packages/cw-orch/Cargo.toml +++ b/packages/cw-orch/Cargo.toml @@ -8,10 +8,12 @@ version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +cosmwasm-std.workspace = true 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" } +cw721-base.workspace = true 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" } @@ -30,3 +32,4 @@ dao-voting-cw4 = { version = "2.4.2", path = "../../contracts/voting/dao-voting- 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" } +serde.workspace = true diff --git a/packages/cw-orch/src/test_contracts/cw721.rs b/packages/cw-orch/src/test_contracts/cw721.rs new file mode 100644 index 000000000..2bd6fc9cd --- /dev/null +++ b/packages/cw-orch/src/test_contracts/cw721.rs @@ -0,0 +1,20 @@ +use cosmwasm_std::Empty; +use cw721_base::{ + entry::{execute, instantiate, query}, + ExecuteMsg, InstantiateMsg, QueryMsg, +}; +use cw_orch::interface; +use cw_orch::prelude::*; + +pub type Cw721BaseQueryMsg = QueryMsg; +#[interface(InstantiateMsg, ExecuteMsg, Cw721BaseQueryMsg, Empty)] +pub struct Cw721BaseGeneric; + +impl Uploadable for Cw721BaseGeneric { + // Return a CosmWasm contract wrapper + fn wrapper() -> Box> { + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query)) + } +} + +pub type Cw721Base = Cw721BaseGeneric, Empty>; diff --git a/packages/cw-orch/src/test_contracts/mod.rs b/packages/cw-orch/src/test_contracts/mod.rs index ac82e1634..674ad6cc6 100644 --- a/packages/cw-orch/src/test_contracts/mod.rs +++ b/packages/cw-orch/src/test_contracts/mod.rs @@ -1,8 +1,10 @@ +mod cw721; mod proposal_hook_counter; mod proposal_sudo; mod test_custom_factory; mod voting_cw20_balance; +pub use cw721::Cw721Base; pub use proposal_hook_counter::DaoProposalHookCounter; pub use proposal_sudo::DaoProposalSudo; pub use test_custom_factory::DaoTestCustomFactory; diff --git a/packages/dao-interface/src/msg.rs b/packages/dao-interface/src/msg.rs index 3e650b8ce..e8027f7c5 100644 --- a/packages/dao-interface/src/msg.rs +++ b/packages/dao-interface/src/msg.rs @@ -148,7 +148,7 @@ pub enum QueryMsg { Config {}, /// Gets the token balance for each cw20 registered with the /// contract. - #[returns(crate::query::Cw20BalanceResponse)] + #[returns(Vec)] Cw20Balances { start_after: Option, limit: Option, @@ -179,7 +179,7 @@ pub enum QueryMsg { /// example, given the items `{ "group": "foo", "subdao": "bar"}` /// this query would return `[("group", "foo"), ("subdao", /// "bar")]`. - #[returns(Vec)] + #[returns(Vec<(String, String)>)] ListItems { start_after: Option, limit: Option, From 3440d34c6a9e84acfd710eb692fdd48c061e3035 Mon Sep 17 00:00:00 2001 From: Kayanski Date: Fri, 17 May 2024 08:06:38 +0000 Subject: [PATCH 4/8] Finalize test changes --- contracts/dao-dao-core/src/cw_orch_tests.rs | 1375 ++++++++----------- 1 file changed, 577 insertions(+), 798 deletions(-) diff --git a/contracts/dao-dao-core/src/cw_orch_tests.rs b/contracts/dao-dao-core/src/cw_orch_tests.rs index 340a91851..66096b85c 100644 --- a/contracts/dao-dao-core/src/cw_orch_tests.rs +++ b/contracts/dao-dao-core/src/cw_orch_tests.rs @@ -1,19 +1,21 @@ use crate::{ contract::{derive_proposal_module_prefix, migrate, CONTRACT_NAME, CONTRACT_VERSION}, + cw_orch_tests::v1::DaoDaoCoreV1, state::PROPOSAL_MODULES, ContractError, }; use abstract_cw20::msg::Cw20ExecuteMsgFns; 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, StdError, Storage, Uint128, WasmMsg, + 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::Cw721Base; @@ -21,7 +23,7 @@ use dao_cw_orch::{DaoDaoCore, DaoProposalSudo, DaoVotingCw20Balance}; use dao_interface::CoreExecuteMsgFns; use dao_interface::CoreQueryMsgFns; use dao_interface::{ - msg::{ExecuteMsg, InitialItem, InstantiateMsg, MigrateMsg, QueryMsg}, + msg::{ExecuteMsg, InitialItem, InstantiateMsg, MigrateMsg}, query::{ AdminNominationResponse, Cw20BalanceResponse, DumpStateResponse, GetItemResponse, PauseInfoResponse, ProposalModuleCountResponse, SubDao, @@ -35,60 +37,31 @@ pub fn assert_contains(e: impl std::fmt::Debug, el: impl ToString) { assert!(format!("{:?}", e).contains(&el.to_string())) } -fn cw20_contract() -> Box> { - let contract = ContractWrapper::new( - cw20_base::contract::execute, - cw20_base::contract::instantiate, - cw20_base::contract::query, - ); - Box::new(contract) -} +pub mod v1 { + use cw_orch::{interface, prelude::*}; -fn cw721_contract() -> Box> { - let contract = ContractWrapper::new( - cw721_base::entry::execute, - cw721_base::entry::instantiate, - cw721_base::entry::query, - ); - Box::new(contract) -} + use cw_core_v1::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; -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) -} + #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] + pub struct DaoDaoCoreV1; -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) + impl Uploadable for DaoDaoCoreV1 { + /// 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> { + use cw_core_v1::contract; + Box::new( + ContractWrapper::new(contract::execute, contract::instantiate, contract::query) + .with_reply(contract::reply) + .with_migrate(contract::migrate), + ) + } + } } fn test_instantiate_with_n_gov_modules(n: usize) { @@ -959,7 +932,7 @@ fn test_admin_permissions() { .unwrap_err(); // Admin can call ExecuteAdminMsgs, here an admin pauses the DAO - let res = core_with_admin + let _res = core_with_admin .call_as(&admin) .execute_admin_msgs(vec![WasmMsg::Execute { contract_addr: core_with_admin.address().unwrap().to_string(), @@ -990,7 +963,7 @@ fn test_admin_permissions() { ); // Admin pauses DAO again - let res = core_with_admin + let _res = core_with_admin .call_as(&admin) .execute_admin_msgs(vec![WasmMsg::Execute { contract_addr: core_with_admin.address().unwrap().to_string(), @@ -1004,16 +977,16 @@ fn test_admin_permissions() { .unwrap(); // DAO with admin cannot unpause itself - let res = core_with_admin + let _res = core_with_admin .call_as(&core_with_admin.address().unwrap()) .unpause() .unwrap_err(); // Random person cannot unpause the DAO - let res = core_with_admin.call_as(&random).unpause().unwrap_err(); + let _res = core_with_admin.call_as(&random).unpause().unwrap_err(); // Admin can unpause the DAO directly - let res = core_with_admin.call_as(&admin).unpause().unwrap(); + let _res = core_with_admin.call_as(&admin).unpause().unwrap(); // Check we are unpaused @@ -1203,26 +1176,6 @@ fn test_passthrough_voting_queries() { ); } -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(); -} - #[test] fn test_item_permissions() { let (gov, _, mock, _) = do_standard_instantiate(true, false); @@ -1240,7 +1193,7 @@ fn test_item_permissions() { #[test] fn test_add_remove_get() { - let (gov, _, mock, _) = do_standard_instantiate(true, false); + let (gov, _, _mock, _) = do_standard_instantiate(true, false); let a = gov.get_item("aaaaa".to_string()).unwrap(); assert_eq!(a, GetItemResponse { item: None }); @@ -1449,7 +1402,7 @@ fn test_instantiate_with_items() { initial_items.pop(); gov_instantiate.initial_items = Some(initial_items); - let gov_addr = gov.instantiate(&gov_instantiate, None, None).unwrap(); + let _gov_addr = gov.instantiate(&gov_instantiate, None, None).unwrap(); // Ensure initial items were added. let items = gov.list_items(None, None).unwrap(); @@ -1473,7 +1426,7 @@ fn test_instantiate_with_items() { #[test] fn test_cw20_receive_auto_add() { - let (gov, proposal, mock, _) = do_standard_instantiate(true, false); + let (gov, _proposal, mock, _) = do_standard_instantiate(true, false); let another_cw20 = Cw20Base::new("another-cw20", mock.clone()); another_cw20.upload().unwrap(); another_cw20 @@ -1558,7 +1511,7 @@ fn test_cw20_receive_auto_add() { #[test] fn test_cw20_receive_no_auto_add() { - let (gov, proposal, mock, _) = do_standard_instantiate(false, false); + let (gov, _proposal, mock, _) = do_standard_instantiate(false, false); let another_cw20 = Cw20Base::new("another-cw20", mock.clone()); another_cw20.upload().unwrap(); @@ -1622,7 +1575,7 @@ fn test_cw20_receive_no_auto_add() { #[test] fn test_cw721_receive() { - let (gov, proposal, mock, _) = do_standard_instantiate(true, false); + let (gov, _proposal, mock, _) = do_standard_instantiate(true, false); let cw721 = Cw721Base::new("cw721", mock.clone()); cw721.upload().unwrap(); @@ -1691,7 +1644,9 @@ fn test_cw721_receive() { ) .unwrap_err(); - // TODO + println!("{:?}", err); + assert_contains(&err, "key:"); + assert_contains(err, "not found"); // assert!(matches!(err, ContractError::Std(_))); // Test that non-DAO can not update the list. @@ -1718,343 +1673,210 @@ fn test_cw721_receive() { ); } -// #[test] -// fn test_cw721_receive_no_auto_add() { -// let (gov, proposal, mock, _) = do_standard_instantiate(false, None); - -// let cw721_id = app.store_code(cw721_contract()); - -// let cw721_addr = app -// .instantiate_contract( -// cw721_id, -// Addr::unchecked(mock.sender()), -// &cw721_base::msg::InstantiateMsg { -// name: "ekez".to_string(), -// symbol: "ekez".to_string(), -// minter: mock.sender().to_string(), -// }, -// &[], -// "cw721", -// None, -// ) -// .unwrap(); - -// let another_cw721 = app -// .instantiate_contract( -// cw721_id, -// Addr::unchecked(mock.sender()), -// &cw721_base::msg::InstantiateMsg { -// name: "ekez".to_string(), -// symbol: "ekez".to_string(), -// minter: mock.sender().to_string(), -// }, -// &[], -// "cw721", -// None, -// ) -// .unwrap(); - -// app.execute_contract( -// Addr::unchecked(mock.sender()), -// cw721_addr.clone(), -// &cw721_base::msg::ExecuteMsg::, Empty>::Mint { -// token_id: "ekez".to_string(), -// owner: mock.sender().to_string(), -// token_uri: None, -// extension: None, -// }, -// &[], -// ) -// .unwrap(); - -// app.execute_contract( -// Addr::unchecked(mock.sender()), -// 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 (gov, proposal, mock, _) = 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_cw721_receive_no_auto_add() { + let (gov, _proposal, mock, _) = do_standard_instantiate(false, false); + + let cw721 = Cw721Base::new("cw721", mock.clone()); + cw721.upload().unwrap(); + cw721 + .instantiate( + &cw721_base::msg::InstantiateMsg { + name: "ekez".to_string(), + symbol: "ekez".to_string(), + minter: mock.sender().to_string(), + }, + None, + None, + ) + .unwrap(); + + let another_cw721 = Cw721Base::new("another_cw721", mock.clone()); + another_cw721.set_code_id(cw721.code_id().unwrap()); + another_cw721 + .instantiate( + &cw721_base::msg::InstantiateMsg { + name: "ekez".to_string(), + symbol: "ekez".to_string(), + minter: mock.sender().to_string(), + }, + None, + None, + ) + .unwrap(); + + assert_eq!( + gov.cw_721_token_list(None, None).unwrap(), + Vec::::new() + ); + + // Duplicates OK. Just adds one. + gov.update_cw_721_list( + vec![ + another_cw721.address().unwrap().to_string(), + cw721.address().unwrap().to_string(), + cw721.address().unwrap().to_string(), + ], + vec![], + ) + .unwrap(); + + assert_eq!( + gov.cw_721_token_list(None, None).unwrap(), + vec![another_cw721.address().unwrap(), cw721.address().unwrap()] + ); +} + +#[test] +fn test_pause() { + let (gov, _proposal, mock, _) = do_standard_instantiate(false, false); + + let start_height = mock.block_info().unwrap().height; + + let proposal_modules = gov.proposal_modules(None, None).unwrap(); + assert_eq!(proposal_modules.len(), 1); + let proposal_module = proposal_modules.into_iter().next().unwrap(); + + assert_eq!(gov.pause_info().unwrap(), PauseInfoResponse::Unpaused {}); + + assert_eq!( + gov.dump_state().unwrap().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. + gov.update_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 = gov + .call_as(&proposal_module.address) + .pause(Duration::Height(10)) + .unwrap_err(); + + // Only the DAO may call this on itself. Proposal modules must use + // the execute hook. + assert_contains(err, ContractError::Unauthorized {}); + gov.call_as(&proposal_module.address) + .execute_proposal_hook(vec![WasmMsg::Execute { + contract_addr: gov.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .unwrap(); + + assert_eq!( + gov.pause_info().unwrap(), + PauseInfoResponse::Paused { + expiration: Expiration::AtHeight(start_height + 10) + } + ); + assert_eq!( + gov.dump_state().unwrap().pause_info, + PauseInfoResponse::Paused { + expiration: Expiration::AtHeight(start_height + 10) + } + ); + + // This should actually be allowed to enable the admin to execute + gov.update_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, + }) + .unwrap(); + + let err = gov + .call_as(&proposal_module.address) + .execute_proposal_hook(vec![WasmMsg::Execute { + contract_addr: gov.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .unwrap_err(); + + assert_contains(err, ContractError::Paused {}); + + mock.wait_blocks(9).unwrap(); + + // Still not unpaused. + + let err = gov + .call_as(&proposal_module.address) + .execute_proposal_hook(vec![WasmMsg::Execute { + contract_addr: gov.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .unwrap_err(); + + assert_contains(err, ContractError::Paused {}); + + mock.wait_blocks(1).unwrap(); + + assert_eq!(gov.pause_info().unwrap(), PauseInfoResponse::Unpaused {}); + assert_eq!( + gov.dump_state().unwrap().pause_info, + PauseInfoResponse::Unpaused {} + ); + + // Now its unpaused so we should be able to pause again. + gov.call_as(&proposal_module.address) + .execute_proposal_hook(vec![WasmMsg::Execute { + contract_addr: gov.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .unwrap(); + + assert_eq!( + gov.pause_info().unwrap(), + PauseInfoResponse::Paused { + expiration: Expiration::AtHeight(start_height + 20) + } + ); + assert_eq!( + gov.dump_state().unwrap().pause_info, + PauseInfoResponse::Paused { + expiration: Expiration::AtHeight(start_height + 20) + } + ); +} #[test] fn test_dump_state_proposal_modules() { - let (gov, proposal, mock, _) = do_standard_instantiate(false, false); + let (gov, _proposal, _mock, _) = do_standard_instantiate(false, false); let proposal_modules = gov.proposal_modules(None, None).unwrap(); assert_eq!(proposal_modules.len(), 1); @@ -2066,385 +1888,342 @@ fn test_dump_state_proposal_modules() { assert_eq!(all_state.proposal_modules[0], proposal_module); } -// // Note that this isn't actually testing that we are migrating from the previous version since -// // with multitest contract instantiation we can't manipulate storage to the previous version of state before invoking migrate. So if anything, -// // this just tests the idempotency of migrate. -// #[test] -// fn test_migrate_from_compatible() { -// let mut app = App::default(); -// 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: mock.sender().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: mock.sender().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(mock.sender()), -// &gov_instantiate, -// &[], -// "cw-governance", -// Some(mock.sender().to_string()), -// ) -// .unwrap(); - -// let state: DumpStateResponse = app -// .wrap() -// .query_wasm_smart(core_addr.clone(), &QueryMsg::DumpState {}) -// .unwrap(); - -// app.execute( -// Addr::unchecked(mock.sender()), -// 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: mock.sender().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: mock.sender().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(mock.sender()), -// &v1_core_instantiate, -// &[], -// "cw-governance", -// Some(mock.sender().to_string()), -// ) -// .unwrap(); - -// app.execute( -// Addr::unchecked(mock.sender()), -// 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(mock.sender()), -// 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 (gov, proposal, mock, _) = 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 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: "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 = gov.instantiate(&gov_instantiate, None, None).unwrap(); - -// let modules = gov.proposal_modules(None, 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); -// } +// 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 mock = MockBech32::new("mock"); + let govmod = DaoProposalSudo::new("proposal", mock.clone()); + let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); + let gov = DaoDaoCore::new("dao-core", mock.clone()); + let cw20 = Cw20Base::new("cw20", mock.clone()); + + govmod.upload().unwrap(); + voting.upload().unwrap(); + gov.upload().unwrap(); + cw20.upload().unwrap(); + + let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { + root: mock.sender().to_string(), + }; + let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { + token_info: dao_voting_cw20_balance::msg::TokenInfo::New { + code_id: cw20.code_id().unwrap(), + label: "DAO DAO voting".to_string(), + name: "DAO DAO".to_string(), + symbol: "DAO".to_string(), + decimals: 6, + initial_balances: vec![cw20::Cw20Coin { + address: mock.sender().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.code_id().unwrap(), + 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.code_id().unwrap(), + 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, Some(&mock.sender()), None) + .unwrap(); + + let state = gov.dump_state().unwrap(); + + gov.migrate(&MigrateMsg::FromCompatible {}, gov.code_id().unwrap()) + .unwrap(); + + let new_state = gov.dump_state().unwrap(); + + assert_eq!(new_state, state); +} + +#[test] +fn test_migrate_from_beta() { + use cw_core_v1 as v1; + + let mock = MockBech32::new("mock"); + let govmod = DaoProposalSudo::new("proposal", mock.clone()); + let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); + let gov = DaoDaoCore::new("dao-core", mock.clone()); + let v1_gov = DaoDaoCoreV1::new("dao-core-v1", mock.clone()); + let cw20 = Cw20Base::new("cw20", mock.clone()); + + govmod.upload().unwrap(); + voting.upload().unwrap(); + gov.upload().unwrap(); + v1_gov.upload().unwrap(); + cw20.upload().unwrap(); + + let proposal_instantiate = dao_proposal_sudo::msg::InstantiateMsg { + root: mock.sender().to_string(), + }; + let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { + token_info: dao_voting_cw20_balance::msg::TokenInfo::New { + code_id: cw20.code_id().unwrap(), + label: "DAO DAO voting".to_string(), + name: "DAO DAO".to_string(), + symbol: "DAO".to_string(), + decimals: 6, + initial_balances: vec![cw20::Cw20Coin { + address: mock.sender().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.code_id().unwrap(), + 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.code_id().unwrap(), + msg: to_json_binary(&proposal_instantiate).unwrap(), + admin: v1::msg::Admin::CoreContract {}, + label: "governance module 1".to_string(), + }, + v1::msg::ModuleInstantiateInfo { + code_id: govmod.code_id().unwrap(), + msg: to_json_binary(&proposal_instantiate).unwrap(), + admin: v1::msg::Admin::CoreContract {}, + label: "governance module 2".to_string(), + }, + ], + initial_items: None, + }; + + v1_gov + .instantiate(&v1_core_instantiate, Some(&mock.sender()), None) + .unwrap(); + + gov.set_address(&v1_gov.address().unwrap()); + gov.migrate( + &MigrateMsg::FromV1 { + dao_uri: None, + params: None, + }, + gov.code_id().unwrap(), + ) + .unwrap(); + + let new_state = gov.dump_state().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 = gov + .migrate( + &MigrateMsg::FromV1 { + dao_uri: None, + params: None, + }, + gov.code_id().unwrap(), + ) + .unwrap_err(); + + assert_contains(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 (gov, _proposal, _mock, _) = do_standard_instantiate(true, false); + let proposal_modules = gov.proposal_modules(None, None).unwrap(); + + assert_eq!(proposal_modules.len(), 1); + let proposal_module = proposal_modules.into_iter().next().unwrap(); + + let res = gov + .call_as(&proposal_module.address) + .execute_proposal_hook(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 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: "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, + }; + + gov.instantiate(&gov_instantiate, None, None).unwrap(); + + let modules = gov.proposal_modules(None, 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, "C"); + 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, "B"); + assert_eq!(&module_3.address, &modules[2].address); +} fn get_active_modules(gov: &DaoDaoCore) -> Vec { let modules = gov.proposal_modules(None, None).unwrap(); From 88ec5ed4919364e8e86a12a23adc536590a40c3e Mon Sep 17 00:00:00 2001 From: Kayanski Date: Fri, 17 May 2024 08:52:54 +0000 Subject: [PATCH 5/8] Added Queryfns on voting --- Cargo.lock | 1 + contracts/dao-dao-core/src/cw_orch_tests.rs | 14 ++++---------- contracts/test/dao-voting-cw20-balance/Cargo.toml | 1 + contracts/test/dao-voting-cw20-balance/src/msg.rs | 2 +- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 35078a4e7..4ce9be36c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2468,6 +2468,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", + "cw-orch", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", diff --git a/contracts/dao-dao-core/src/cw_orch_tests.rs b/contracts/dao-dao-core/src/cw_orch_tests.rs index 66096b85c..aab42583e 100644 --- a/contracts/dao-dao-core/src/cw_orch_tests.rs +++ b/contracts/dao-dao-core/src/cw_orch_tests.rs @@ -32,6 +32,7 @@ use dao_interface::{ voting::{InfoResponse, VotingPowerAtHeightResponse}, }; use dao_proposal_sudo::msg::ExecuteMsgFns as _; +use dao_voting_cw20_balance::msg::QueryMsgFns; pub fn assert_contains(e: impl std::fmt::Debug, el: impl ToString) { assert!(format!("{:?}", e).contains(&el.to_string())) @@ -1448,11 +1449,8 @@ fn test_cw20_receive_auto_add() { voting.set_address(&gov.voting_module().unwrap()); let gov_token = Cw20Base::new("cw20", mock.clone()); - gov_token.set_address( - &voting - .query(&dao_voting_cw20_balance::msg::QueryMsg::TokenContract {}) - .unwrap(), - ); + + gov_token.set_address(&voting.token_contract().unwrap()); // Check that the balances query works with no tokens. let cw20_balances = gov.cw_20_balances(None, None).unwrap(); assert_eq!(cw20_balances, vec![]); @@ -1534,11 +1532,7 @@ fn test_cw20_receive_no_auto_add() { voting.set_address(&gov.voting_module().unwrap()); let gov_token = Cw20Base::new("cw20", mock.clone()); - gov_token.set_address( - &voting - .query(&dao_voting_cw20_balance::msg::QueryMsg::TokenContract {}) - .unwrap(), - ); + gov_token.set_address(&voting.token_contract().unwrap()); // Send a gov token to the governance contract. Should not be // added becasue auto add is turned off. diff --git a/contracts/test/dao-voting-cw20-balance/Cargo.toml b/contracts/test/dao-voting-cw20-balance/Cargo.toml index f126b6840..77e55bf88 100644 --- a/contracts/test/dao-voting-cw20-balance/Cargo.toml +++ b/contracts/test/dao-voting-cw20-balance/Cargo.toml @@ -27,6 +27,7 @@ thiserror = { workspace = true } dao-dao-macros = { workspace = true } dao-interface = { workspace = true } cw20-base = { workspace = true, features = ["library"] } +cw-orch = "0.22.2" [dev-dependencies] cw-multi-test = { workspace = true } diff --git a/contracts/test/dao-voting-cw20-balance/src/msg.rs b/contracts/test/dao-voting-cw20-balance/src/msg.rs index c41590ee2..43002a7ba 100644 --- a/contracts/test/dao-voting-cw20-balance/src/msg.rs +++ b/contracts/test/dao-voting-cw20-balance/src/msg.rs @@ -33,5 +33,5 @@ pub enum ExecuteMsg {} #[cw20_token_query] #[voting_module_query] #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg {} From 003f87da1958c411630d03529d08e3e927c3ff4e Mon Sep 17 00:00:00 2001 From: Kayanski Date: Fri, 17 May 2024 08:54:07 +0000 Subject: [PATCH 6/8] Fix tests --- contracts/dao-dao-core/src/cw_orch_tests.rs | 2314 ---------------- contracts/dao-dao-core/src/lib.rs | 2 - contracts/dao-dao-core/src/tests.rs | 2715 +++++++------------ 3 files changed, 915 insertions(+), 4116 deletions(-) delete mode 100644 contracts/dao-dao-core/src/cw_orch_tests.rs diff --git a/contracts/dao-dao-core/src/cw_orch_tests.rs b/contracts/dao-dao-core/src/cw_orch_tests.rs deleted file mode 100644 index aab42583e..000000000 --- a/contracts/dao-dao-core/src/cw_orch_tests.rs +++ /dev/null @@ -1,2314 +0,0 @@ -use crate::{ - contract::{derive_proposal_module_prefix, migrate, CONTRACT_NAME, CONTRACT_VERSION}, - cw_orch_tests::v1::DaoDaoCoreV1, - state::PROPOSAL_MODULES, - ContractError, -}; -use abstract_cw20::msg::Cw20ExecuteMsgFns; -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_orch::prelude::*; - -use cw_storage_plus::{Item, Map}; -use cw_utils::{Duration, Expiration}; -use dao_cw_orch::Cw721Base; -use dao_cw_orch::{DaoDaoCore, DaoProposalSudo, DaoVotingCw20Balance}; -use dao_interface::CoreExecuteMsgFns; -use dao_interface::CoreQueryMsgFns; -use dao_interface::{ - msg::{ExecuteMsg, InitialItem, InstantiateMsg, MigrateMsg}, - query::{ - AdminNominationResponse, Cw20BalanceResponse, DumpStateResponse, GetItemResponse, - PauseInfoResponse, ProposalModuleCountResponse, SubDao, - }, - state::{Admin, Config, ModuleInstantiateInfo, ProposalModule, ProposalModuleStatus}, - voting::{InfoResponse, VotingPowerAtHeightResponse}, -}; -use dao_proposal_sudo::msg::ExecuteMsgFns as _; -use dao_voting_cw20_balance::msg::QueryMsgFns; - -pub fn assert_contains(e: impl std::fmt::Debug, el: impl ToString) { - assert!(format!("{:?}", e).contains(&el.to_string())) -} - -pub mod v1 { - use cw_orch::{interface, prelude::*}; - - use cw_core_v1::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; - - #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] - pub struct DaoDaoCoreV1; - - impl Uploadable for DaoDaoCoreV1 { - /// 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> { - use cw_core_v1::contract; - Box::new( - ContractWrapper::new(contract::execute, contract::instantiate, contract::query) - .with_reply(contract::reply) - .with_migrate(contract::migrate), - ) - } - } -} - -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); - - 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(); - - assert_contains( - err, - ContractError::ModuleDisabledCannotExecute { - address: Addr::unchecked(""), - }, - ); - - // 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 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_disable = vec![ - start_module.address.to_string(), - start_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: 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(); - - assert_contains( - err, - ContractError::ModuleAlreadyDisabled { - address: start_module.address, - }, - ); -} - -#[test] -fn test_swap_voting_module() { - 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); - gov_mod.set_address(&modules[0].address); - - let voting_addr = gov.voting_module().unwrap(); - - gov_mod - .proposal_execute(vec![WasmMsg::Execute { - contract_addr: gov.address().unwrap().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(); - - assert_ne!(gov.voting_module().unwrap(), voting_addr); -} - -fn test_unauthorized(gov: &DaoDaoCore, msg: ExecuteMsg) { - let err = gov.execute(&msg, None).unwrap_err(); - - assert_contains(err, ContractError::Unauthorized {}); -} - -#[test] -fn test_permissions() { - 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, - 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, - }; - - gov.instantiate(&gov_instantiate, None, None).unwrap(); - - test_unauthorized( - &gov, - 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( - &gov, - ExecuteMsg::UpdateProposalModules { - to_add: vec![], - to_disable: vec![], - }, - ); - - test_unauthorized( - &gov, - 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: bool, -) -> ( - DaoDaoCore, - DaoProposalSudo, - MockBech32, - Option, -) { - let mock = MockBech32::new("mock"); - let gov_mod = DaoProposalSudo::new("proposal", mock.clone()); - let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); - let mut gov = DaoDaoCore::new("dao-core", mock.clone()); - let cw20 = Cw20Base::new("cw20", mock.clone()); - - gov_mod.upload().unwrap(); - voting.upload().unwrap(); - gov.upload().unwrap(); - cw20.upload().unwrap(); - - let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { - root: mock.sender().to_string(), - }; - let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { - token_info: dao_voting_cw20_balance::msg::TokenInfo::New { - code_id: cw20.code_id().unwrap(), - label: "DAO DAO voting".to_string(), - name: "DAO DAO".to_string(), - symbol: "DAO".to_string(), - decimals: 6, - initial_balances: vec![cw20::Cw20Coin { - address: mock.sender().to_string(), - amount: Uint128::from(2u64), - }], - marketing: None, - }, - }; - let admin = admin.then(|| mock.addr_make("admin")); - - let gov_instantiate = InstantiateMsg { - dao_uri: None, - admin: admin.as_ref().map(|a| a.to_string()), - 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.code_id().unwrap(), - 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: gov_mod.code_id().unwrap(), - 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 proposal_modules = gov.proposal_modules(None, None).unwrap(); - assert_eq!(proposal_modules.len(), 1); - let proposal_module = proposal_modules.into_iter().next().unwrap(); - gov_mod.set_address(&proposal_module.address); - - if admin.is_none() { - gov = gov.call_as(&gov.address().unwrap()); - } - - (gov, gov_mod, mock, admin) -} - -#[test] -fn test_admin_permissions() { - let (core, proposal, mock, _) = do_standard_instantiate(true, false); - - let random = mock.addr_make("random"); - let start_height = mock.block_info().unwrap().height; - - // Random address can't call ExecuteAdminMsgs - core.call_as(&random) - .execute_admin_msgs(vec![WasmMsg::Execute { - contract_addr: core.address().unwrap().to_string(), - msg: to_json_binary(&ExecuteMsg::Pause { - duration: Duration::Height(10), - }) - .unwrap(), - funds: vec![], - } - .into()]) - .unwrap_err(); - - // Proposal module can't call ExecuteAdminMsgs - core.call_as(&proposal.address().unwrap()) - .execute_admin_msgs(vec![WasmMsg::Execute { - contract_addr: core.address().unwrap().to_string(), - msg: to_json_binary(&ExecuteMsg::Pause { - duration: Duration::Height(10), - }) - .unwrap(), - funds: vec![], - } - .into()]) - .unwrap_err(); - - // Update Admin can't be called by non-admins - core.call_as(&random) - .nominate_admin(Some(random.to_string())) - .unwrap_err(); - - // Nominate admin can be called by core contract as no admin was - // specified so the admin defaulted to the core contract. - - core.call_as(&proposal.address().unwrap()) - .execute_proposal_hook(vec![WasmMsg::Execute { - contract_addr: core.address().unwrap().to_string(), - msg: to_json_binary(&ExecuteMsg::Pause { - duration: Duration::Height(10), - }) - .unwrap(), - funds: vec![], - } - .into()]) - .unwrap(); - - // Instantiate new DAO with an admin - let (core_with_admin, proposal_with_admin_address, mock, admin) = - do_standard_instantiate(true, true); - let admin = admin.unwrap(); - - // Non admins still can't call ExecuteAdminMsgs - core_with_admin - .call_as(&proposal_with_admin_address.address().unwrap()) - .execute_admin_msgs(vec![WasmMsg::Execute { - contract_addr: core_with_admin.address().unwrap().to_string(), - msg: to_json_binary(&ExecuteMsg::Pause { - duration: Duration::Height(10), - }) - .unwrap(), - funds: vec![], - } - .into()]) - .unwrap_err(); - - // Admin cannot directly pause the DAO - core_with_admin - .call_as(&admin) - .pause(Duration::Height(10)) - .unwrap_err(); - - // Random person cannot pause the DAO - core_with_admin - .call_as(&random) - .pause(Duration::Height(10)) - .unwrap_err(); - - // Admin can call ExecuteAdminMsgs, here an admin pauses the DAO - let _res = core_with_admin - .call_as(&admin) - .execute_admin_msgs(vec![WasmMsg::Execute { - contract_addr: core_with_admin.address().unwrap().to_string(), - msg: to_json_binary(&ExecuteMsg::Pause { - duration: Duration::Height(10), - }) - .unwrap(), - funds: vec![], - } - .into()]) - .unwrap(); - - // Ensure we are paused for 10 blocks - assert_eq!( - core_with_admin.pause_info().unwrap(), - PauseInfoResponse::Paused { - expiration: Expiration::AtHeight(start_height + 10) - } - ); - - // DAO unpauses after 10 blocks - mock.wait_blocks(11).unwrap(); - - // Check we are unpaused - assert_eq!( - core_with_admin.pause_info().unwrap(), - PauseInfoResponse::Unpaused {} - ); - - // Admin pauses DAO again - let _res = core_with_admin - .call_as(&admin) - .execute_admin_msgs(vec![WasmMsg::Execute { - contract_addr: core_with_admin.address().unwrap().to_string(), - msg: to_json_binary(&ExecuteMsg::Pause { - duration: Duration::Height(10), - }) - .unwrap(), - funds: vec![], - } - .into()]) - .unwrap(); - - // DAO with admin cannot unpause itself - let _res = core_with_admin - .call_as(&core_with_admin.address().unwrap()) - .unpause() - .unwrap_err(); - - // Random person cannot unpause the DAO - let _res = core_with_admin.call_as(&random).unpause().unwrap_err(); - - // Admin can unpause the DAO directly - let _res = core_with_admin.call_as(&admin).unpause().unwrap(); - - // Check we are unpaused - - assert_eq!( - core_with_admin.pause_info().unwrap(), - PauseInfoResponse::Unpaused {} - ); - - // Admin can nominate a new admin. - let new_admin = mock.addr_make("meow"); - core_with_admin - .call_as(&admin) - .nominate_admin(Some(new_admin.to_string())) - .unwrap(); - - assert_eq!( - core_with_admin.admin_nomination().unwrap(), - AdminNominationResponse { - nomination: Some(new_admin.clone()) - } - ); - - // Check that admin has not yet been updated - assert_eq!(core_with_admin.admin().unwrap(), admin); - - // Only the nominated address may accept the nomination. - let err = core_with_admin - .call_as(&random) - .accept_admin_nomination() - .unwrap_err(); - - assert_contains(err, ContractError::Unauthorized {}); - - // Accept the nomination. - core_with_admin - .call_as(&new_admin) - .accept_admin_nomination() - .unwrap(); - - // Check that admin has been updated - assert_eq!(core_with_admin.admin().unwrap(), new_admin); - - // Check that the pending admin has been cleared. - assert_eq!( - core_with_admin.admin_nomination().unwrap(), - AdminNominationResponse { nomination: None } - ); -} - -#[test] -fn test_admin_nomination() { - let (core, _, mock, admin) = do_standard_instantiate(true, true); - - let admin = admin.unwrap(); - // Check that there is no pending nominations. - assert_eq!( - core.admin_nomination().unwrap(), - AdminNominationResponse { nomination: None } - ); - - // Nominate a new admin. - let ekez = mock.addr_make("ekez"); - core.call_as(&admin) - .nominate_admin(Some(ekez.to_string())) - .unwrap(); - - // Check that the nomination is in place. - assert_eq!( - core.admin_nomination().unwrap(), - AdminNominationResponse { - nomination: Some(ekez.clone()) - } - ); - - // Non-admin can not withdraw. - let err = core.call_as(&ekez).withdraw_admin_nomination().unwrap_err(); - assert_contains(err, ContractError::Unauthorized {}); - - // Admin can withdraw. - core.call_as(&admin).withdraw_admin_nomination().unwrap(); - - // Check that the nomination is withdrawn. - assert_eq!( - core.admin_nomination().unwrap(), - AdminNominationResponse { nomination: None } - ); - - // Can not withdraw if no nomination is pending. - let err = core - .call_as(&admin) - .withdraw_admin_nomination() - .unwrap_err(); - - assert_contains(err, ContractError::NoAdminNomination {}); - - // Can not claim nomination b/c it has been withdrawn. - let err = core.call_as(&admin).accept_admin_nomination().unwrap_err(); - - assert_contains(err, ContractError::NoAdminNomination {}); - - // Nominate a new admin. - let meow = mock.addr_make("meow"); - core.call_as(&admin) - .nominate_admin(Some(meow.to_string())) - .unwrap(); - - // A new nomination can not be created if there is already a - // pending nomination. - let err = core - .call_as(&admin) - .nominate_admin(Some(ekez.to_string())) - .unwrap_err(); - assert_contains(err, ContractError::PendingNomination {}); - - // Only nominated admin may accept. - let err = core.call_as(&ekez).accept_admin_nomination().unwrap_err(); - assert_contains(err, ContractError::Unauthorized {}); - - core.call_as(&meow).accept_admin_nomination().unwrap(); - - // Check that meow is the new admin. - assert_eq!(core.admin().unwrap(), meow); - - let start_height = mock.block_info().unwrap().height; - // Check that the new admin can do admin things and the old can not. - let err = core - .call_as(&admin) - .execute_admin_msgs(vec![WasmMsg::Execute { - contract_addr: core.address().unwrap().to_string(), - msg: to_json_binary(&ExecuteMsg::Pause { - duration: Duration::Height(10), - }) - .unwrap(), - funds: vec![], - } - .into()]) - .unwrap_err(); - assert_contains(err, ContractError::Unauthorized {}); - - core.call_as(&meow) - .execute_admin_msgs(vec![WasmMsg::Execute { - contract_addr: core.address().unwrap().to_string(), - msg: to_json_binary(&ExecuteMsg::Pause { - duration: Duration::Height(10), - }) - .unwrap(), - funds: vec![], - } - .into()]) - .unwrap(); - - assert_eq!( - core.pause_info().unwrap(), - PauseInfoResponse::Paused { - expiration: Expiration::AtHeight(start_height + 10) - } - ); - - // DAO unpauses after 10 blocks - mock.wait_blocks(11).unwrap(); - - // Remove the admin. - core.call_as(&meow).nominate_admin(None).unwrap(); - - // Check that this has not caused an admin to be nominated. - assert_eq!( - core.admin_nomination().unwrap(), - AdminNominationResponse { nomination: None } - ); - - // Check that admin has been updated. As there was no admin - // nominated the admin should revert back to the contract address. - assert_eq!(core.admin().unwrap(), core.address().unwrap()); -} - -#[test] -fn test_passthrough_voting_queries() { - let (gov, _, mock, _) = do_standard_instantiate(true, false); - - assert_eq!( - gov.voting_power_at_height(mock.sender().to_string(), None) - .unwrap(), - VotingPowerAtHeightResponse { - power: Uint128::from(2u64), - height: mock.block_info().unwrap().height, - } - ); -} - -#[test] -fn test_item_permissions() { - let (gov, _, mock, _) = do_standard_instantiate(true, false); - - let ekez = mock.addr_make("ekez"); - let err = gov - .call_as(&ekez) - .set_item("k".to_string(), "v".to_string()) - .unwrap_err(); - assert_contains(err, ContractError::Unauthorized {}); - - let err = gov.call_as(&ekez).remove_item("k".to_string()).unwrap_err(); - assert_contains(err, ContractError::Unauthorized {}); -} - -#[test] -fn test_add_remove_get() { - let (gov, _, _mock, _) = do_standard_instantiate(true, false); - - let a = gov.get_item("aaaaa".to_string()).unwrap(); - assert_eq!(a, GetItemResponse { item: None }); - - gov.set_item("aaaaakey".to_string(), "aaaaaaddr".to_string()) - .unwrap(); - let a = gov.get_item("aaaaakey".to_string()).unwrap(); - assert_eq!( - a, - GetItemResponse { - item: Some("aaaaaaddr".to_string()) - } - ); - - gov.remove_item("aaaaakey".to_string()).unwrap(); - let a = gov.get_item("aaaaakey".to_string()).unwrap(); - assert_eq!(a, GetItemResponse { item: None }); -} - -#[test] -#[should_panic(expected = "Key is missing from storage")] -fn test_remove_missing_key() { - let (gov, _, _, _) = do_standard_instantiate(true, false); - gov.remove_item("b".to_string()).unwrap(); -} - -#[test] -fn test_list_items() { - let mock = MockBech32::new("mock"); - let govmod = DaoProposalSudo::new("proposal", mock.clone()); - let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); - let gov = DaoDaoCore::new("dao-core", mock.clone()); - let cw20 = Cw20Base::new("cw20", mock.clone()); - - govmod.upload().unwrap(); - voting.upload().unwrap(); - gov.upload().unwrap(); - cw20.upload().unwrap(); - let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { - root: mock.sender().to_string(), - }; - let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { - token_info: dao_voting_cw20_balance::msg::TokenInfo::New { - code_id: cw20.code_id().unwrap(), - label: "DAO DAO voting".to_string(), - name: "DAO DAO".to_string(), - symbol: "DAO".to_string(), - decimals: 6, - initial_balances: vec![cw20::Cw20Coin { - address: mock.sender().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.code_id().unwrap(), - 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.code_id().unwrap(), - 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 gov = gov.call_as(&gov.address().unwrap()); - - gov.set_item("fookey".to_string(), "fooaddr".to_string()) - .unwrap(); - gov.set_item("barkey".to_string(), "baraddr".to_string()) - .unwrap(); - gov.set_item("loremkey".to_string(), "loremaddr".to_string()) - .unwrap(); - gov.set_item("ipsumkey".to_string(), "ipsumaddr".to_string()) - .unwrap(); - - // Foo returned as we are only getting one item and items are in - // decending order. - let first_item = gov.list_items(Some(1), None).unwrap(); - assert_eq!(first_item.len(), 1); - assert_eq!( - first_item[0], - ("loremkey".to_string(), "loremaddr".to_string()) - ); - - let no_items = gov.list_items(Some(0), None).unwrap(); - 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 = gov.list_items(None, Some("foo".to_string())).unwrap(); - - 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 = gov.list_items(Some(4), Some("ipsum".to_string())).unwrap(); - 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 mock = MockBech32::new("mock"); - let govmod = DaoProposalSudo::new("proposal", mock.clone()); - let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); - let gov = DaoDaoCore::new("dao-core", mock.clone()); - let cw20 = Cw20Base::new("cw20", mock.clone()); - - govmod.upload().unwrap(); - voting.upload().unwrap(); - gov.upload().unwrap(); - cw20.upload().unwrap(); - - let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { - root: mock.sender().to_string(), - }; - let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { - token_info: dao_voting_cw20_balance::msg::TokenInfo::New { - code_id: cw20.code_id().unwrap(), - label: "DAO DAO voting".to_string(), - name: "DAO DAO".to_string(), - symbol: "DAO".to_string(), - decimals: 6, - initial_balances: vec![cw20::Cw20Coin { - address: mock.sender().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.code_id().unwrap(), - 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.code_id().unwrap(), - 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 = gov.instantiate(&gov_instantiate, None, None).unwrap_err(); - assert_contains( - err, - ContractError::DuplicateInitialItem { - item: "item0".to_string(), - }, - ); - - initial_items.pop(); - gov_instantiate.initial_items = Some(initial_items); - let _gov_addr = gov.instantiate(&gov_instantiate, None, None).unwrap(); - - // Ensure initial items were added. - let items = gov.list_items(None, None).unwrap(); - assert_eq!(items.len(), 2); - - // Descending order, so item1 is first. - assert_eq!(items[1].0, "item0".to_string()); - let get_item0 = gov.get_item("item0".to_string()).unwrap(); - - assert_eq!( - get_item0, - GetItemResponse { - item: Some("item0_value".to_string()), - } - ); - - assert_eq!(items[0].0, "item1".to_string()); - let item1_value = gov.get_item("item1".to_string()).unwrap().item; - assert_eq!(item1_value, Some("item1_value".to_string())) -} - -#[test] -fn test_cw20_receive_auto_add() { - let (gov, _proposal, mock, _) = do_standard_instantiate(true, false); - let another_cw20 = Cw20Base::new("another-cw20", mock.clone()); - another_cw20.upload().unwrap(); - another_cw20 - .instantiate( - &abstract_cw20_base::msg::InstantiateMsg { - name: "DAO".to_string(), - symbol: "DAO".to_string(), - decimals: 6, - initial_balances: vec![], - mint: None, - marketing: None, - }, - None, - None, - ) - .unwrap(); - - let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); - voting.set_address(&gov.voting_module().unwrap()); - - let gov_token = Cw20Base::new("cw20", mock.clone()); - - gov_token.set_address(&voting.token_contract().unwrap()); - // Check that the balances query works with no tokens. - let cw20_balances = gov.cw_20_balances(None, None).unwrap(); - assert_eq!(cw20_balances, vec![]); - - // Send a gov token to the governance contract. - gov_token - .send( - Uint128::new(1), - gov.address().unwrap().to_string(), - to_json_binary(&"").unwrap(), - ) - .unwrap(); - - let cw20_list = gov.cw_20_token_list(None, None).unwrap(); - assert_eq!( - cw20_list, - vec![gov_token.address().unwrap().to_string().clone()] - ); - - assert_eq!( - gov.cw_20_balances(None, None).unwrap(), - vec![Cw20BalanceResponse { - addr: gov_token.address().unwrap(), - balance: Uint128::new(1), - }] - ); - - // Test removing and adding some new ones. Invalid should fail. - let err = gov - .update_cw_20_list( - vec![mock.addr_make("new").to_string()], - vec![gov_token.address().unwrap().to_string()], - ) - .unwrap_err(); - println!("{:?}", err); - assert_contains(&err, "key:"); - assert_contains(err, "not found"); - - // Test that non-DAO can not update the list. - let err = gov - .call_as(&mock.addr_make("ekez")) - .update_cw_20_list(vec![], vec![gov_token.address().unwrap().to_string()]) - .unwrap_err(); - - assert_contains(err, ContractError::Unauthorized {}); - - gov.update_cw_20_list( - vec![another_cw20.address().unwrap().to_string()], - vec![gov_token.address().unwrap().to_string()], - ) - .unwrap(); - - let cw20_list = gov.cw_20_token_list(None, None).unwrap(); - assert_eq!(cw20_list, vec![another_cw20.address().unwrap().to_string()]); -} - -#[test] -fn test_cw20_receive_no_auto_add() { - let (gov, _proposal, mock, _) = do_standard_instantiate(false, false); - - let another_cw20 = Cw20Base::new("another-cw20", mock.clone()); - another_cw20.upload().unwrap(); - another_cw20 - .instantiate( - &abstract_cw20_base::msg::InstantiateMsg { - name: "DAO".to_string(), - symbol: "DAO".to_string(), - decimals: 6, - initial_balances: vec![], - mint: None, - marketing: None, - }, - None, - None, - ) - .unwrap(); - - let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); - voting.set_address(&gov.voting_module().unwrap()); - - let gov_token = Cw20Base::new("cw20", mock.clone()); - gov_token.set_address(&voting.token_contract().unwrap()); - - // Send a gov token to the governance contract. Should not be - // added becasue auto add is turned off. - gov_token - .send( - Uint128::new(1), - gov.address().unwrap().to_string(), - to_json_binary(&"").unwrap(), - ) - .unwrap(); - - assert_eq!( - gov.cw_20_token_list(None, None).unwrap(), - Vec::::new() - ); - - gov.update_cw_20_list( - vec![ - another_cw20.address().unwrap().to_string(), - gov_token.address().unwrap().to_string(), - ], - vec![mock.addr_make("ok to remove non existent").to_string()], - ) - .unwrap(); - - assert_eq!( - gov.cw_20_token_list(None, None).unwrap(), - vec![ - gov_token.address().unwrap(), - another_cw20.address().unwrap(), - ] - ); -} - -#[test] -fn test_cw721_receive() { - let (gov, _proposal, mock, _) = do_standard_instantiate(true, false); - - let cw721 = Cw721Base::new("cw721", mock.clone()); - cw721.upload().unwrap(); - cw721 - .instantiate( - &cw721_base::msg::InstantiateMsg { - name: "ekez".to_string(), - symbol: "ekez".to_string(), - minter: mock.sender().to_string(), - }, - None, - None, - ) - .unwrap(); - - let another_cw721 = Cw721Base::new("another_cw721", mock.clone()); - another_cw721.set_code_id(cw721.code_id().unwrap()); - another_cw721 - .instantiate( - &cw721_base::msg::InstantiateMsg { - name: "ekez".to_string(), - symbol: "ekez".to_string(), - minter: mock.sender().to_string(), - }, - None, - None, - ) - .unwrap(); - - cw721 - .execute( - &cw721_base::msg::ExecuteMsg::, Empty>::Mint { - token_id: "ekez".to_string(), - owner: mock.sender().to_string(), - token_uri: None, - extension: None, - }, - None, - ) - .unwrap(); - - cw721 - .execute( - &cw721_base::msg::ExecuteMsg::, Empty>::SendNft { - contract: gov.address().unwrap().to_string(), - token_id: "ekez".to_string(), - msg: to_json_binary("").unwrap(), - }, - None, - ) - .unwrap(); - - assert_eq!( - gov.cw_721_token_list(None, None).unwrap(), - vec![cw721.address().unwrap().clone()] - ); - - // Try to add an invalid cw721. - let err = gov - .update_cw_721_list( - vec![ - mock.addr_make("new").to_string(), - cw721.address().unwrap().clone().to_string(), - ], - vec![cw721.address().unwrap().clone().to_string()], - ) - .unwrap_err(); - - println!("{:?}", err); - assert_contains(&err, "key:"); - assert_contains(err, "not found"); - // assert!(matches!(err, ContractError::Std(_))); - - // Test that non-DAO can not update the list. - let err = gov - .call_as(&mock.addr_make("ekez")) - .update_cw_721_list(vec![], vec![cw721.address().unwrap().clone().to_string()]) - .unwrap_err(); - - assert_contains(err, ContractError::Unauthorized {}); - - // Add a real cw721. - gov.update_cw_721_list( - vec![ - cw721.address().unwrap().to_string(), - another_cw721.address().unwrap().to_string(), - ], - vec![cw721.address().unwrap().to_string()], - ) - .unwrap(); - - assert_eq!( - gov.cw_721_token_list(None, None).unwrap(), - vec![another_cw721.address().unwrap()] - ); -} - -#[test] -fn test_cw721_receive_no_auto_add() { - let (gov, _proposal, mock, _) = do_standard_instantiate(false, false); - - let cw721 = Cw721Base::new("cw721", mock.clone()); - cw721.upload().unwrap(); - cw721 - .instantiate( - &cw721_base::msg::InstantiateMsg { - name: "ekez".to_string(), - symbol: "ekez".to_string(), - minter: mock.sender().to_string(), - }, - None, - None, - ) - .unwrap(); - - let another_cw721 = Cw721Base::new("another_cw721", mock.clone()); - another_cw721.set_code_id(cw721.code_id().unwrap()); - another_cw721 - .instantiate( - &cw721_base::msg::InstantiateMsg { - name: "ekez".to_string(), - symbol: "ekez".to_string(), - minter: mock.sender().to_string(), - }, - None, - None, - ) - .unwrap(); - - assert_eq!( - gov.cw_721_token_list(None, None).unwrap(), - Vec::::new() - ); - - // Duplicates OK. Just adds one. - gov.update_cw_721_list( - vec![ - another_cw721.address().unwrap().to_string(), - cw721.address().unwrap().to_string(), - cw721.address().unwrap().to_string(), - ], - vec![], - ) - .unwrap(); - - assert_eq!( - gov.cw_721_token_list(None, None).unwrap(), - vec![another_cw721.address().unwrap(), cw721.address().unwrap()] - ); -} - -#[test] -fn test_pause() { - let (gov, _proposal, mock, _) = do_standard_instantiate(false, false); - - let start_height = mock.block_info().unwrap().height; - - let proposal_modules = gov.proposal_modules(None, None).unwrap(); - assert_eq!(proposal_modules.len(), 1); - let proposal_module = proposal_modules.into_iter().next().unwrap(); - - assert_eq!(gov.pause_info().unwrap(), PauseInfoResponse::Unpaused {}); - - assert_eq!( - gov.dump_state().unwrap().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. - gov.update_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 = gov - .call_as(&proposal_module.address) - .pause(Duration::Height(10)) - .unwrap_err(); - - // Only the DAO may call this on itself. Proposal modules must use - // the execute hook. - assert_contains(err, ContractError::Unauthorized {}); - gov.call_as(&proposal_module.address) - .execute_proposal_hook(vec![WasmMsg::Execute { - contract_addr: gov.address().unwrap().to_string(), - msg: to_json_binary(&ExecuteMsg::Pause { - duration: Duration::Height(10), - }) - .unwrap(), - funds: vec![], - } - .into()]) - .unwrap(); - - assert_eq!( - gov.pause_info().unwrap(), - PauseInfoResponse::Paused { - expiration: Expiration::AtHeight(start_height + 10) - } - ); - assert_eq!( - gov.dump_state().unwrap().pause_info, - PauseInfoResponse::Paused { - expiration: Expiration::AtHeight(start_height + 10) - } - ); - - // This should actually be allowed to enable the admin to execute - gov.update_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, - }) - .unwrap(); - - let err = gov - .call_as(&proposal_module.address) - .execute_proposal_hook(vec![WasmMsg::Execute { - contract_addr: gov.address().unwrap().to_string(), - msg: to_json_binary(&ExecuteMsg::Pause { - duration: Duration::Height(10), - }) - .unwrap(), - funds: vec![], - } - .into()]) - .unwrap_err(); - - assert_contains(err, ContractError::Paused {}); - - mock.wait_blocks(9).unwrap(); - - // Still not unpaused. - - let err = gov - .call_as(&proposal_module.address) - .execute_proposal_hook(vec![WasmMsg::Execute { - contract_addr: gov.address().unwrap().to_string(), - msg: to_json_binary(&ExecuteMsg::Pause { - duration: Duration::Height(10), - }) - .unwrap(), - funds: vec![], - } - .into()]) - .unwrap_err(); - - assert_contains(err, ContractError::Paused {}); - - mock.wait_blocks(1).unwrap(); - - assert_eq!(gov.pause_info().unwrap(), PauseInfoResponse::Unpaused {}); - assert_eq!( - gov.dump_state().unwrap().pause_info, - PauseInfoResponse::Unpaused {} - ); - - // Now its unpaused so we should be able to pause again. - gov.call_as(&proposal_module.address) - .execute_proposal_hook(vec![WasmMsg::Execute { - contract_addr: gov.address().unwrap().to_string(), - msg: to_json_binary(&ExecuteMsg::Pause { - duration: Duration::Height(10), - }) - .unwrap(), - funds: vec![], - } - .into()]) - .unwrap(); - - assert_eq!( - gov.pause_info().unwrap(), - PauseInfoResponse::Paused { - expiration: Expiration::AtHeight(start_height + 20) - } - ); - assert_eq!( - gov.dump_state().unwrap().pause_info, - PauseInfoResponse::Paused { - expiration: Expiration::AtHeight(start_height + 20) - } - ); -} - -#[test] -fn test_dump_state_proposal_modules() { - let (gov, _proposal, _mock, _) = do_standard_instantiate(false, false); - let proposal_modules = gov.proposal_modules(None, None).unwrap(); - - assert_eq!(proposal_modules.len(), 1); - let proposal_module = proposal_modules.into_iter().next().unwrap(); - - let all_state: DumpStateResponse = gov.dump_state().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 mock = MockBech32::new("mock"); - let govmod = DaoProposalSudo::new("proposal", mock.clone()); - let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); - let gov = DaoDaoCore::new("dao-core", mock.clone()); - let cw20 = Cw20Base::new("cw20", mock.clone()); - - govmod.upload().unwrap(); - voting.upload().unwrap(); - gov.upload().unwrap(); - cw20.upload().unwrap(); - - let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { - root: mock.sender().to_string(), - }; - let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { - token_info: dao_voting_cw20_balance::msg::TokenInfo::New { - code_id: cw20.code_id().unwrap(), - label: "DAO DAO voting".to_string(), - name: "DAO DAO".to_string(), - symbol: "DAO".to_string(), - decimals: 6, - initial_balances: vec![cw20::Cw20Coin { - address: mock.sender().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.code_id().unwrap(), - 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.code_id().unwrap(), - 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, Some(&mock.sender()), None) - .unwrap(); - - let state = gov.dump_state().unwrap(); - - gov.migrate(&MigrateMsg::FromCompatible {}, gov.code_id().unwrap()) - .unwrap(); - - let new_state = gov.dump_state().unwrap(); - - assert_eq!(new_state, state); -} - -#[test] -fn test_migrate_from_beta() { - use cw_core_v1 as v1; - - let mock = MockBech32::new("mock"); - let govmod = DaoProposalSudo::new("proposal", mock.clone()); - let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); - let gov = DaoDaoCore::new("dao-core", mock.clone()); - let v1_gov = DaoDaoCoreV1::new("dao-core-v1", mock.clone()); - let cw20 = Cw20Base::new("cw20", mock.clone()); - - govmod.upload().unwrap(); - voting.upload().unwrap(); - gov.upload().unwrap(); - v1_gov.upload().unwrap(); - cw20.upload().unwrap(); - - let proposal_instantiate = dao_proposal_sudo::msg::InstantiateMsg { - root: mock.sender().to_string(), - }; - let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { - token_info: dao_voting_cw20_balance::msg::TokenInfo::New { - code_id: cw20.code_id().unwrap(), - label: "DAO DAO voting".to_string(), - name: "DAO DAO".to_string(), - symbol: "DAO".to_string(), - decimals: 6, - initial_balances: vec![cw20::Cw20Coin { - address: mock.sender().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.code_id().unwrap(), - 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.code_id().unwrap(), - msg: to_json_binary(&proposal_instantiate).unwrap(), - admin: v1::msg::Admin::CoreContract {}, - label: "governance module 1".to_string(), - }, - v1::msg::ModuleInstantiateInfo { - code_id: govmod.code_id().unwrap(), - msg: to_json_binary(&proposal_instantiate).unwrap(), - admin: v1::msg::Admin::CoreContract {}, - label: "governance module 2".to_string(), - }, - ], - initial_items: None, - }; - - v1_gov - .instantiate(&v1_core_instantiate, Some(&mock.sender()), None) - .unwrap(); - - gov.set_address(&v1_gov.address().unwrap()); - gov.migrate( - &MigrateMsg::FromV1 { - dao_uri: None, - params: None, - }, - gov.code_id().unwrap(), - ) - .unwrap(); - - let new_state = gov.dump_state().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 = gov - .migrate( - &MigrateMsg::FromV1 { - dao_uri: None, - params: None, - }, - gov.code_id().unwrap(), - ) - .unwrap_err(); - - assert_contains(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 (gov, _proposal, _mock, _) = do_standard_instantiate(true, false); - let proposal_modules = gov.proposal_modules(None, None).unwrap(); - - assert_eq!(proposal_modules.len(), 1); - let proposal_module = proposal_modules.into_iter().next().unwrap(); - - let res = gov - .call_as(&proposal_module.address) - .execute_proposal_hook(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 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: "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, - }; - - gov.instantiate(&gov_instantiate, None, None).unwrap(); - - let modules = gov.proposal_modules(None, 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, "C"); - 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, "B"); - 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 (gov, _proposal, mock, _) = do_standard_instantiate(false, false); - - test_unauthorized( - &gov.call_as(&mock.sender()), - ExecuteMsg::UpdateSubDaos { - to_add: vec![], - to_remove: vec![], - }, - ); - - let to_add: Vec = vec![ - SubDao { - addr: mock.addr_make("subdao001").to_string(), - charter: None, - }, - SubDao { - addr: mock.addr_make("subdao002").to_string(), - charter: Some("cool charter bro".to_string()), - }, - SubDao { - addr: mock.addr_make("subdao005").to_string(), - charter: None, - }, - SubDao { - addr: mock.addr_make("subdao007").to_string(), - charter: None, - }, - ]; - let to_remove: Vec = vec![]; - - gov.update_sub_daos(to_add, to_remove).unwrap(); - - assert_eq!(gov.list_sub_daos(None, None).unwrap().len(), 4); - - let to_remove: Vec = vec![mock.addr_make("subdao005").to_string()]; - - gov.update_sub_daos(vec![], to_remove).unwrap(); - - let res = gov.list_sub_daos(None, None).unwrap(); - - assert_eq!(res.len(), 3); - let full_result_set: Vec = vec![ - SubDao { - addr: mock.addr_make("subdao001").to_string(), - charter: None, - }, - SubDao { - addr: mock.addr_make("subdao002").to_string(), - charter: Some("cool charter bro".to_string()), - }, - SubDao { - addr: mock.addr_make("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 (gov, _, _, _) = do_standard_instantiate(true, false); - assert_eq!( - gov.info().unwrap(), - 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 e1d62aad4..20a9a57d7 100644 --- a/contracts/dao-dao-core/src/lib.rs +++ b/contracts/dao-dao-core/src/lib.rs @@ -4,8 +4,6 @@ pub mod contract; mod error; pub mod state; -#[cfg(test)] -mod cw_orch_tests; #[cfg(test)] mod tests; diff --git a/contracts/dao-dao-core/src/tests.rs b/contracts/dao-dao-core/src/tests.rs index 81dd040cb..e20eab146 100644 --- a/contracts/dao-dao-core/src/tests.rs +++ b/contracts/dao-dao-core/src/tests.rs @@ -1,3 +1,12 @@ +use crate::{ + contract::{derive_proposal_module_prefix, migrate, CONTRACT_NAME, CONTRACT_VERSION}, + state::PROPOSAL_MODULES, + ContractError, +}; +use abstract_cw20::msg::Cw20ExecuteMsgFns; +use abstract_cw_plus_interface::cw20_base::Cw20Base; +use v1::DaoDaoCoreV1; + use cosmwasm_schema::cw_serde; use cosmwasm_std::{ from_json, @@ -5,99 +14,64 @@ use cosmwasm_std::{ 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::Cw721Base; +use dao_cw_orch::{DaoDaoCore, DaoProposalSudo, DaoVotingCw20Balance}; +use dao_interface::CoreExecuteMsgFns; +use dao_interface::CoreQueryMsgFns; use dao_interface::{ - msg::{ExecuteMsg, InitialItem, InstantiateMsg, MigrateMsg, QueryMsg}, + msg::{ExecuteMsg, InitialItem, InstantiateMsg, MigrateMsg}, query::{ - AdminNominationResponse, Cw20BalanceResponse, DaoURIResponse, DumpStateResponse, - GetItemResponse, PauseInfoResponse, ProposalModuleCountResponse, SubDao, + AdminNominationResponse, Cw20BalanceResponse, DumpStateResponse, GetItemResponse, + PauseInfoResponse, ProposalModuleCountResponse, SubDao, }, state::{Admin, Config, ModuleInstantiateInfo, ProposalModule, ProposalModuleStatus}, voting::{InfoResponse, VotingPowerAtHeightResponse}, }; +use dao_proposal_sudo::msg::ExecuteMsgFns as _; +use dao_voting_cw20_balance::msg::QueryMsgFns; -use crate::{ - contract::{derive_proposal_module_prefix, migrate, CONTRACT_NAME, CONTRACT_VERSION}, - state::PROPOSAL_MODULES, - ContractError, -}; - -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) +pub fn assert_contains(e: impl std::fmt::Debug, el: impl ToString) { + assert!(format!("{:?}", e).contains(&el.to_string())) } -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) -} +pub mod v1 { + use cw_orch::{interface, prelude::*}; -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) -} + use cw_core_v1::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; -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) -} + #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] + pub struct DaoDaoCoreV1; -fn instantiate_gov(app: &mut App, code_id: u64, msg: InstantiateMsg) -> Addr { - app.instantiate_contract( - code_id, - Addr::unchecked(CREATOR_ADDR), - &msg, - &[], - "cw-governance", - None, - ) - .unwrap() + impl Uploadable for DaoDaoCoreV1 { + /// 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> { + use cw_core_v1::contract; + Box::new( + ContractWrapper::new(contract::execute, contract::instantiate, contract::query) + .with_reply(contract::reply) + .with_migrate(contract::migrate), + ) + } + } } fn test_instantiate_with_n_gov_modules(n: usize) { - let mut app = App::default(); - let cw20_id = app.store_code(cw20_contract()); - let gov_id = app.store_code(cw_core_contract()); + 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(), @@ -133,12 +107,9 @@ fn test_instantiate_with_n_gov_modules(n: usize) { .collect(), initial_items: None, }; - let gov_addr = instantiate_gov(&mut app, gov_id, instantiate); + gov.instantiate(&instantiate, None, None).unwrap(); - let state: DumpStateResponse = app - .wrap() - .query_wasm_smart(gov_addr, &QueryMsg::DumpState {}) - .unwrap(); + let state = gov.dump_state().unwrap(); assert_eq!( state.config, @@ -173,11 +144,16 @@ fn test_valid_instantiate() { } #[test] -#[should_panic(expected = "Error parsing into type cw20_base::msg::InstantiateMsg: Invalid type")] +#[should_panic( + expected = "Error parsing into type abstract_cw20_base::msg::InstantiateMsg: Invalid type" +)] fn test_instantiate_with_submessage_failure() { - let mut app = App::default(); - let cw20_id = app.store_code(cw20_contract()); - let gov_id = app.store_code(cw_core_contract()); + 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(), @@ -233,17 +209,21 @@ makes wickedness." proposal_modules_instantiate_info: governance_modules, initial_items: None, }; - instantiate_gov(&mut app, gov_id, instantiate); + + gov.instantiate(&instantiate, None, None).unwrap(); } #[test] -fn test_update_config() { - let mut app = App::default(); - let govmod_id = app.store_code(sudo_proposal_contract()); - let gov_id = app.store_code(cw_core_contract()); +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: CREATOR_ADDR.to_string(), + root: mock.sender().to_string(), }; let gov_instantiate = InstantiateMsg { @@ -256,14 +236,14 @@ fn test_update_config() { automatically_add_cw721s: true, voting_module_instantiate_info: ModuleInstantiateInfo { code_id: govmod_id, - msg: to_json_binary(&govmod_instantiate).unwrap(), + 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).unwrap(), + msg: to_json_binary(&govmod_instantiate)?, admin: Some(Admin::CoreModule {}), funds: vec![], label: "voting module".to_string(), @@ -271,29 +251,11 @@ fn test_update_config() { 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(); + 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(), @@ -304,45 +266,31 @@ fn test_update_config() { dao_uri: Some("https://daostar.one/EIP".to_string()), }; - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - modules[0].clone().address, - &dao_proposal_sudo::msg::ExecuteMsg::Execute { - msgs: vec![WasmMsg::Execute { - contract_addr: gov_addr.to_string(), - funds: vec![], - msg: to_json_binary(&ExecuteMsg::UpdateConfig { - config: expected_config.clone(), - }) - .unwrap(), - } - .into()], - }, - &[], - ) - .unwrap(); - - let config: Config = app - .wrap() - .query_wasm_smart(gov_addr.clone(), &QueryMsg::Config {}) - .unwrap(); + 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, config); + assert_eq!(expected_config, gov.config()?); - let dao_uri: DaoURIResponse = app - .wrap() - .query_wasm_smart(gov_addr, &QueryMsg::DaoURI {}) - .unwrap(); - assert_eq!(dao_uri.dao_uri, expected_config.dao_uri); + assert_eq!(gov.dao_uri()?.dao_uri, expected_config.dao_uri); + Ok(()) } fn test_swap_governance(swaps: Vec<(u32, u32)>) { - let mut app = App::default(); - let propmod_id = app.store_code(sudo_proposal_contract()); - let core_id = app.store_code(cw_core_contract()); + 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: CREATOR_ADDR.to_string(), + root: mock.sender().to_string(), }; let gov_instantiate = InstantiateMsg { @@ -370,31 +318,12 @@ fn test_swap_governance(swaps: Vec<(u32, u32)>) { initial_items: None, }; - let gov_addr = app - .instantiate_contract( - core_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(); + 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(); - let module_count = query_proposal_module_count(&app, &gov_addr); assert_eq!( module_count, ProposalModuleCountResponse { @@ -410,19 +339,12 @@ fn test_swap_governance(swaps: Vec<(u32, u32)>) { .unwrap_or((0, 0)); for (add, remove) in swaps { - let start_modules: Vec = app - .wrap() - .query_wasm_smart( - gov_addr.clone(), - &QueryMsg::ProposalModules { - start_after: None, - limit: None, - }, - ) - .unwrap(); + let start_modules = gov.proposal_modules(None, None).unwrap(); - let start_modules_active: Vec = get_active_modules(&app, gov_addr.clone()); + let start_modules_active: Vec = get_active_modules(&gov); + 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, @@ -440,23 +362,17 @@ fn test_swap_governance(swaps: Vec<(u32, u32)>) { .map(|a| a.address.to_string()) .collect(); - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - start_modules_active[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::UpdateProposalModules { to_add, to_disable }) - .unwrap(), - } - .into()], - }, - &[], - ) - .unwrap(); + 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(&app, gov_addr.clone()); + let finish_modules_active = get_active_modules(&gov); assert_eq!( finish_modules_active.len() as u32, @@ -471,11 +387,7 @@ fn test_swap_governance(swaps: Vec<(u32, u32)>) { assert!(!finish_modules_active.contains(&module)) } - let state: DumpStateResponse = app - .wrap() - .query_wasm_smart(gov_addr.clone(), &QueryMsg::DumpState {}) - .unwrap(); - + let state: DumpStateResponse = gov.dump_state().unwrap(); assert_eq!( state.active_proposal_module_count, finish_modules_active.len() as u32 @@ -487,7 +399,7 @@ fn test_swap_governance(swaps: Vec<(u32, u32)>) { ) } - let module_count = query_proposal_module_count(&app, &gov_addr); + let module_count = gov.proposal_module_count().unwrap(); assert_eq!( module_count, ProposalModuleCountResponse { @@ -516,12 +428,15 @@ fn test_swap_governance_bad() { #[test] fn test_removed_modules_can_not_execute() { - let mut app = App::default(); - let govmod_id = app.store_code(sudo_proposal_contract()); - let gov_id = app.store_code(cw_core_contract()); + 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: CREATOR_ADDR.to_string(), + root: mock.sender().to_string(), }; let gov_instantiate = InstantiateMsg { @@ -549,31 +464,14 @@ fn test_removed_modules_can_not_execute() { initial_items: None, }; - let gov_addr = app - .instantiate_contract( - gov_id, - Addr::unchecked(CREATOR_ADDR), - &gov_instantiate, - &[], - "cw-governance", - None, - ) - .unwrap(); + gov.instantiate(&gov_instantiate, None, None).unwrap(); - let modules: Vec = app - .wrap() - .query_wasm_smart( - gov_addr.clone(), - &QueryMsg::ProposalModules { - start_after: None, - limit: 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, @@ -586,23 +484,16 @@ fn test_removed_modules_can_not_execute() { let to_disable = vec![start_module.address.to_string()]; // Swap ourselves out. - 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, to_disable }) - .unwrap(), - } - .into()], - }, - &[], - ) - .unwrap(); + 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: Vec = get_active_modules(&app, gov_addr.clone()); + let finish_modules_active = get_active_modules(&gov); let new_proposal_module = finish_modules_active.into_iter().next().unwrap(); @@ -617,74 +508,54 @@ fn test_removed_modules_can_not_execute() { }]; let to_disable = vec![new_proposal_module.address.to_string()]; - let err: ContractError = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - start_module.address, - &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: to_add.clone(), - to_disable: to_disable.clone(), - }) - .unwrap(), - } - .into()], - }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!( + 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(); + + assert_contains( err, ContractError::ModuleDisabledCannotExecute { - address: _gov_address - } - )); + address: Addr::unchecked(""), + }, + ); // Check that the enabled query works. - let enabled_modules: Vec = app - .wrap() - .query_wasm_smart( - &gov_addr, - &QueryMsg::ActiveProposalModules { - start_after: None, - limit: None, - }, - ) - .unwrap(); + 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. - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - new_proposal_module.address, - &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, to_disable }) - .unwrap(), - } - .into()], - }, - &[], - ) - .unwrap(); + 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 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: CREATOR_ADDR.to_string(), + root: mock.sender().to_string(), }; let gov_instantiate = InstantiateMsg { @@ -712,81 +583,56 @@ fn test_module_already_disabled() { 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(); - + 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_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(), + 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: vec![ModuleInstantiateInfo { + code_id: govmod_id, + msg: to_json_binary(&govmod_instantiate).unwrap(), + admin: Some(Admin::CoreModule {}), 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(); + label: "governance module".to_string(), + }], + to_disable, + }) + .unwrap(), + } + .into()]) + .unwrap_err(); - assert_eq!( + assert_contains( err, ContractError::ModuleAlreadyDisabled { - address: start_module.address - } - ) + 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 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: CREATOR_ADDR.to_string(), + root: mock.sender().to_string(), }; let gov_instantiate = InstantiateMsg { @@ -814,85 +660,51 @@ fn test_swap_voting_module() { 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(); - + gov.instantiate(&gov_instantiate, None, None).unwrap(); + let modules = gov.proposal_modules(None, None).unwrap(); assert_eq!(modules.len(), 1); + gov_mod.set_address(&modules[0].address); - 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 voting_addr = gov.voting_module().unwrap(); - let new_voting_addr: Addr = app - .wrap() - .query_wasm_smart(gov_addr, &QueryMsg::VotingModule {}) + gov_mod + .proposal_execute(vec![WasmMsg::Execute { + contract_addr: gov.address().unwrap().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(); - assert_ne!(new_voting_addr, voting_addr); + assert_ne!(gov.voting_module().unwrap(), 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(); +fn test_unauthorized(gov: &DaoDaoCore, msg: ExecuteMsg) { + let err = gov.execute(&msg, None).unwrap_err(); - assert_eq!(err, ContractError::Unauthorized {}); + assert_contains(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 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: CREATOR_ADDR.to_string(), + root: mock.sender().to_string(), }; let gov_instantiate = InstantiateMsg { @@ -920,20 +732,10 @@ fn test_permissions() { automatically_add_cw721s: true, }; - let gov_addr = app - .instantiate_contract( - gov_id, - Addr::unchecked(CREATOR_ADDR), - &gov_instantiate, - &[], - "cw-governance", - None, - ) - .unwrap(); + gov.instantiate(&gov_instantiate, None, None).unwrap(); test_unauthorized( - &mut app, - gov_addr.clone(), + &gov, ExecuteMsg::UpdateVotingModule { module: ModuleInstantiateInfo { code_id: govmod_id, @@ -946,8 +748,7 @@ fn test_permissions() { ); test_unauthorized( - &mut app, - gov_addr.clone(), + &gov, ExecuteMsg::UpdateProposalModules { to_add: vec![], to_disable: vec![], @@ -955,8 +756,7 @@ fn test_permissions() { ); test_unauthorized( - &mut app, - gov_addr, + &gov, ExecuteMsg::UpdateConfig { config: Config { dao_uri: None, @@ -970,48 +770,62 @@ fn test_permissions() { ); } -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()); +fn do_standard_instantiate( + auto_add: bool, + admin: bool, +) -> ( + DaoDaoCore, + DaoProposalSudo, + MockBech32, + Option, +) { + let mock = MockBech32::new("mock"); + let gov_mod = DaoProposalSudo::new("proposal", mock.clone()); + let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); + let mut gov = DaoDaoCore::new("dao-core", mock.clone()); + let cw20 = Cw20Base::new("cw20", mock.clone()); + + gov_mod.upload().unwrap(); + voting.upload().unwrap(); + gov.upload().unwrap(); + cw20.upload().unwrap(); let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { - root: CREATOR_ADDR.to_string(), + root: mock.sender().to_string(), }; let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { token_info: dao_voting_cw20_balance::msg::TokenInfo::New { - code_id: cw20_id, + code_id: cw20.code_id().unwrap(), 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(), + address: mock.sender().to_string(), amount: Uint128::from(2u64), }], marketing: None, }, }; + let admin = admin.then(|| mock.addr_make("admin")); let gov_instantiate = InstantiateMsg { dao_uri: None, - admin, + admin: admin.as_ref().map(|a| a.to_string()), 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, + code_id: voting.code_id().unwrap(), 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, + code_id: gov_mod.code_id().unwrap(), msg: to_json_binary(&govmod_instantiate).unwrap(), admin: Some(Admin::CoreModule {}), funds: vec![], @@ -1020,653 +834,374 @@ fn do_standard_instantiate(auto_add: bool, admin: Option) -> (Addr, App) initial_items: None, }; - let gov_addr = app - .instantiate_contract( - gov_id, - Addr::unchecked(CREATOR_ADDR), - &gov_instantiate, - &[], - "cw-governance", - None, - ) - .unwrap(); + gov.instantiate(&gov_instantiate, None, None).unwrap(); + + let proposal_modules = gov.proposal_modules(None, None).unwrap(); + assert_eq!(proposal_modules.len(), 1); + let proposal_module = proposal_modules.into_iter().next().unwrap(); + gov_mod.set_address(&proposal_module.address); + + if admin.is_none() { + gov = gov.call_as(&gov.address().unwrap()); + } - (gov_addr, app) + (gov, gov_mod, mock, admin) } #[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(); + let (core, proposal, mock, _) = do_standard_instantiate(true, false); - assert_eq!(proposal_modules.len(), 1); - let proposal_module = proposal_modules.into_iter().next().unwrap(); + let random = mock.addr_make("random"); + let start_height = mock.block_info().unwrap().height; // 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(); + core.call_as(&random) + .execute_admin_msgs(vec![WasmMsg::Execute { + contract_addr: core.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .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(); + core.call_as(&proposal.address().unwrap()) + .execute_admin_msgs(vec![WasmMsg::Execute { + contract_addr: core.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .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(); + core.call_as(&random) + .nominate_admin(Some(random.to_string())) + .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(); + + core.call_as(&proposal.address().unwrap()) + .execute_proposal_hook(vec![WasmMsg::Execute { + contract_addr: core.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .unwrap(); // Instantiate new DAO with an admin - let (core_with_admin_addr, mut app) = - do_standard_instantiate(true, Some(Addr::unchecked("admin").to_string())); + let (core_with_admin, proposal_with_admin_address, mock, admin) = + do_standard_instantiate(true, true); + let admin = admin.unwrap(); // 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(); + core_with_admin + .call_as(&proposal_with_admin_address.address().unwrap()) + .execute_admin_msgs(vec![WasmMsg::Execute { + contract_addr: core_with_admin.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .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()); + core_with_admin + .call_as(&admin) + .pause(Duration::Height(10)) + .unwrap_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()); + core_with_admin + .call_as(&random) + .pause(Duration::Height(10)) + .unwrap_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()); + let _res = core_with_admin + .call_as(&admin) + .execute_admin_msgs(vec![WasmMsg::Execute { + contract_addr: core_with_admin.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .unwrap(); // 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, + core_with_admin.pause_info().unwrap(), PauseInfoResponse::Paused { expiration: Expiration::AtHeight(start_height + 10) } ); // DAO unpauses after 10 blocks - app.update_block(|block| block.height += 11); + mock.wait_blocks(11).unwrap(); // 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 {}); + assert_eq!( + core_with_admin.pause_info().unwrap(), + 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()); + let _res = core_with_admin + .call_as(&admin) + .execute_admin_msgs(vec![WasmMsg::Execute { + contract_addr: core_with_admin.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .unwrap(); // 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()); + let _res = core_with_admin + .call_as(&core_with_admin.address().unwrap()) + .unpause() + .unwrap_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()); + let _res = core_with_admin.call_as(&random).unpause().unwrap_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()); + let _res = core_with_admin.call_as(&admin).unpause().unwrap(); // 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()), - }, - &[], + assert_eq!( + core_with_admin.pause_info().unwrap(), + PauseInfoResponse::Unpaused {} ); - res.unwrap(); - let nomination: AdminNominationResponse = app - .wrap() - .query_wasm_smart(core_with_admin_addr.clone(), &QueryMsg::AdminNomination {}) + // Admin can nominate a new admin. + let new_admin = mock.addr_make("meow"); + core_with_admin + .call_as(&admin) + .nominate_admin(Some(new_admin.to_string())) .unwrap(); + assert_eq!( - nomination, + core_with_admin.admin_nomination().unwrap(), AdminNominationResponse { - nomination: Some(Addr::unchecked("meow")) + nomination: Some(new_admin.clone()) } ); // 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")); + assert_eq!(core_with_admin.admin().unwrap(), 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 {}); + let err = core_with_admin + .call_as(&random) + .accept_admin_nomination() + .unwrap_err(); + + assert_contains(err, ContractError::Unauthorized {}); // Accept the nomination. - app.execute_contract( - Addr::unchecked("meow"), - core_with_admin_addr.clone(), - &ExecuteMsg::AcceptAdminNomination {}, - &[], - ) - .unwrap(); + core_with_admin + .call_as(&new_admin) + .accept_admin_nomination() + .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")); + assert_eq!(core_with_admin.admin().unwrap(), new_admin); // 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 }); + assert_eq!( + core_with_admin.admin_nomination().unwrap(), + AdminNominationResponse { nomination: None } + ); } #[test] fn test_admin_nomination() { - let (core_addr, mut app) = do_standard_instantiate(true, Some("admin".to_string())); + let (core, _, mock, admin) = do_standard_instantiate(true, true); + let admin = admin.unwrap(); // 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 }); + assert_eq!( + core.admin_nomination().unwrap(), + AdminNominationResponse { nomination: None } + ); // Nominate a new admin. - app.execute_contract( - Addr::unchecked("admin"), - core_addr.clone(), - &ExecuteMsg::NominateAdmin { - admin: Some("ekez".to_string()), - }, - &[], - ) - .unwrap(); + let ekez = mock.addr_make("ekez"); + core.call_as(&admin) + .nominate_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, + core.admin_nomination().unwrap(), AdminNominationResponse { - nomination: Some(Addr::unchecked("ekez")) + nomination: Some(ekez.clone()) } ); // 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 {}); + let err = core.call_as(&ekez).withdraw_admin_nomination().unwrap_err(); + assert_contains(err, ContractError::Unauthorized {}); // Admin can withdraw. - app.execute_contract( - Addr::unchecked("admin"), - core_addr.clone(), - &ExecuteMsg::WithdrawAdminNomination {}, - &[], - ) - .unwrap(); + core.call_as(&admin).withdraw_admin_nomination().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 }); + assert_eq!( + core.admin_nomination().unwrap(), + 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 {}); + let err = core + .call_as(&admin) + .withdraw_admin_nomination() + .unwrap_err(); + + assert_contains(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 {}); + let err = core.call_as(&admin).accept_admin_nomination().unwrap_err(); + + assert_contains(err, ContractError::NoAdminNomination {}); // Nominate a new admin. - app.execute_contract( - Addr::unchecked("admin"), - core_addr.clone(), - &ExecuteMsg::NominateAdmin { - admin: Some("meow".to_string()), - }, - &[], - ) - .unwrap(); + let meow = mock.addr_make("meow"); + core.call_as(&admin) + .nominate_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 {}); + let err = core + .call_as(&admin) + .nominate_admin(Some(ekez.to_string())) + .unwrap_err(); + assert_contains(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 {}); + let err = core.call_as(&ekez).accept_admin_nomination().unwrap_err(); + assert_contains(err, ContractError::Unauthorized {}); - app.execute_contract( - Addr::unchecked("meow"), - core_addr.clone(), - &ExecuteMsg::AcceptAdminNomination {}, - &[], - ) - .unwrap(); + core.call_as(&meow).accept_admin_nomination().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())); + assert_eq!(core.admin().unwrap(), meow); - let start_height = app.block_info().height; + let start_height = mock.block_info().unwrap().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() + let err = core + .call_as(&admin) + .execute_admin_msgs(vec![WasmMsg::Execute { + contract_addr: core.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .unwrap_err(); + assert_contains(err, ContractError::Unauthorized {}); + + core.call_as(&meow) + .execute_admin_msgs(vec![WasmMsg::Execute { + contract_addr: core.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) .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, + core.pause_info().unwrap(), PauseInfoResponse::Paused { expiration: Expiration::AtHeight(start_height + 10) } ); // DAO unpauses after 10 blocks - app.update_block(|block| block.height += 11); + mock.wait_blocks(11).unwrap(); // Remove the admin. - app.execute_contract( - Addr::unchecked("meow"), - core_addr.clone(), - &ExecuteMsg::NominateAdmin { admin: None }, - &[], - ) - .unwrap(); + core.call_as(&meow).nominate_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 }); + assert_eq!( + core.admin_nomination().unwrap(), + 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); + assert_eq!(core.admin().unwrap(), core.address().unwrap()); } #[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(); + let (gov, _, mock, _) = do_standard_instantiate(true, false); assert_eq!( - creator_voting_power, + gov.voting_power_at_height(mock.sender().to_string(), None) + .unwrap(), VotingPowerAtHeightResponse { power: Uint128::from(2u64), - height: app.block_info().height, + height: mock.block_info().unwrap().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 {}); + let (gov, _, mock, _) = do_standard_instantiate(true, false); + + let ekez = mock.addr_make("ekez"); + let err = gov + .call_as(&ekez) + .set_item("k".to_string(), "v".to_string()) + .unwrap_err(); + assert_contains(err, ContractError::Unauthorized {}); + + let err = gov.call_as(&ekez).remove_item("k".to_string()).unwrap_err(); + assert_contains(err, ContractError::Unauthorized {}); } #[test] fn test_add_remove_get() { - let (gov_addr, mut app) = do_standard_instantiate(true, None); + let (gov, _, _mock, _) = do_standard_instantiate(true, false); - let a = get_item(&mut app, gov_addr.clone(), "aaaaa".to_string()); + let a = gov.get_item("aaaaa".to_string()).unwrap(); 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()); + gov.set_item("aaaaakey".to_string(), "aaaaaaddr".to_string()) + .unwrap(); + let a = gov.get_item("aaaaakey".to_string()).unwrap(); assert_eq!( a, GetItemResponse { @@ -1674,38 +1209,42 @@ fn test_add_remove_get() { } ); - remove_item(&mut app, gov_addr.clone(), "aaaaakey".to_string()); - let a = get_item(&mut app, gov_addr, "aaaaakey".to_string()); + gov.remove_item("aaaaakey".to_string()).unwrap(); + let a = gov.get_item("aaaaakey".to_string()).unwrap(); 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()) + let (gov, _, _, _) = do_standard_instantiate(true, false); + gov.remove_item("b".to_string()).unwrap(); } #[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 mock = MockBech32::new("mock"); + let govmod = DaoProposalSudo::new("proposal", mock.clone()); + let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); + let gov = DaoDaoCore::new("dao-core", mock.clone()); + let cw20 = Cw20Base::new("cw20", mock.clone()); + + govmod.upload().unwrap(); + voting.upload().unwrap(); + gov.upload().unwrap(); + cw20.upload().unwrap(); let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { - root: CREATOR_ADDR.to_string(), + root: mock.sender().to_string(), }; let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { token_info: dao_voting_cw20_balance::msg::TokenInfo::New { - code_id: cw20_id, + code_id: cw20.code_id().unwrap(), 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(), + address: mock.sender().to_string(), amount: Uint128::from(2u64), }], marketing: None, @@ -1721,14 +1260,14 @@ fn test_list_items() { automatically_add_cw20s: true, automatically_add_cw721s: true, voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: voting_id, + code_id: voting.code_id().unwrap(), 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, + code_id: govmod.code_id().unwrap(), msg: to_json_binary(&govmod_instantiate).unwrap(), admin: Some(Admin::CoreModule {}), funds: vec![], @@ -1737,64 +1276,41 @@ fn test_list_items() { initial_items: None, }; - let gov_addr = app - .instantiate_contract( - gov_id, - Addr::unchecked(CREATOR_ADDR), - &gov_instantiate, - &[], - "cw-governance", - None, - ) - .unwrap(); + gov.instantiate(&gov_instantiate, None, None).unwrap(); + let gov = gov.call_as(&gov.address().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(), - ); + gov.set_item("fookey".to_string(), "fooaddr".to_string()) + .unwrap(); + gov.set_item("barkey".to_string(), "baraddr".to_string()) + .unwrap(); + gov.set_item("loremkey".to_string(), "loremaddr".to_string()) + .unwrap(); + gov.set_item("ipsumkey".to_string(), "ipsumaddr".to_string()) + .unwrap(); // 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)); + let first_item = gov.list_items(Some(1), None).unwrap(); 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)); + let no_items = gov.list_items(Some(0), None).unwrap(); 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); + let last_item = gov.list_items(None, Some("foo".to_string())).unwrap(); + 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)); + let after_foo_list = gov.list_items(Some(4), Some("ipsum".to_string())).unwrap(); assert_eq!(after_foo_list.len(), 2); assert_eq!( after_foo_list, @@ -1807,24 +1323,29 @@ fn test_list_items() { #[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 mock = MockBech32::new("mock"); + let govmod = DaoProposalSudo::new("proposal", mock.clone()); + let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); + let gov = DaoDaoCore::new("dao-core", mock.clone()); + let cw20 = Cw20Base::new("cw20", mock.clone()); + + govmod.upload().unwrap(); + voting.upload().unwrap(); + gov.upload().unwrap(); + cw20.upload().unwrap(); let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { - root: CREATOR_ADDR.to_string(), + root: mock.sender().to_string(), }; let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { token_info: dao_voting_cw20_balance::msg::TokenInfo::New { - code_id: cw20_id, + code_id: cw20.code_id().unwrap(), 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(), + address: mock.sender().to_string(), amount: Uint128::from(2u64), }], marketing: None, @@ -1855,14 +1376,14 @@ fn test_instantiate_with_items() { automatically_add_cw20s: true, automatically_add_cw721s: true, voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: voting_id, + code_id: voting.code_id().unwrap(), 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, + code_id: govmod.code_id().unwrap(), msg: to_json_binary(&govmod_instantiate).unwrap(), admin: Some(Admin::CoreModule {}), funds: vec![], @@ -1872,45 +1393,26 @@ fn test_instantiate_with_items() { }; // 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!( + let err = gov.instantiate(&gov_instantiate, None, None).unwrap_err(); + assert_contains( err, ContractError::DuplicateInitialItem { - item: "item0".to_string() - } + 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(); + let _gov_addr = gov.instantiate(&gov_instantiate, None, None).unwrap(); // Ensure initial items were added. - let items = list_items(&mut app, gov_addr.clone(), None, None); + let items = gov.list_items(None, None).unwrap(); 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()); + let get_item0 = gov.get_item("item0".to_string()).unwrap(); + assert_eq!( get_item0, GetItemResponse { @@ -1919,20 +1421,18 @@ fn test_instantiate_with_items() { ); assert_eq!(items[0].0, "item1".to_string()); - let item1_value = get_item(&mut app, gov_addr, "item1".to_string()).item; + let item1_value = gov.get_item("item1".to_string()).unwrap().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 { + let (gov, _proposal, mock, _) = do_standard_instantiate(true, false); + let another_cw20 = Cw20Base::new("another-cw20", mock.clone()); + another_cw20.upload().unwrap(); + another_cw20 + .instantiate( + &abstract_cw20_base::msg::InstantiateMsg { name: "DAO".to_string(), symbol: "DAO".to_string(), decimals: 6, @@ -1940,146 +1440,82 @@ fn test_cw20_receive_auto_add() { mint: None, marketing: None, }, - &[], - "another-token", + None, 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(); + let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); + voting.set_address(&gov.voting_module().unwrap()); + + let gov_token = Cw20Base::new("cw20", mock.clone()); + gov_token.set_address(&voting.token_contract().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(); + let cw20_balances = gov.cw_20_balances(None, 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, - }, + gov_token + .send( + Uint128::new(1), + gov.address().unwrap().to_string(), + to_json_binary(&"").unwrap(), ) .unwrap(); + + let cw20_list = gov.cw_20_token_list(None, None).unwrap(); + assert_eq!( + cw20_list, + vec![gov_token.address().unwrap().to_string().clone()] + ); + assert_eq!( - cw20_balances, + gov.cw_20_balances(None, None).unwrap(), vec![Cw20BalanceResponse { - addr: gov_token.clone(), + addr: gov_token.address().unwrap(), 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()], - }, - &[], + let err = gov + .update_cw_20_list( + vec![mock.addr_make("new").to_string()], + vec![gov_token.address().unwrap().to_string()], ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!(err, ContractError::Std(_))); + .unwrap_err(); + println!("{:?}", err); + assert_contains(&err, "key:"); + assert_contains(err, "not found"); // 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()], - }, - &[], + let err = gov + .call_as(&mock.addr_make("ekez")) + .update_cw_20_list(vec![], vec![gov_token.address().unwrap().to_string()]) + .unwrap_err(); + + assert_contains(err, ContractError::Unauthorized {}); + + gov.update_cw_20_list( + vec![another_cw20.address().unwrap().to_string()], + vec![gov_token.address().unwrap().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]); + let cw20_list = gov.cw_20_token_list(None, None).unwrap(); + assert_eq!(cw20_list, vec![another_cw20.address().unwrap().to_string()]); } #[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 { + let (gov, _proposal, mock, _) = do_standard_instantiate(false, false); + + let another_cw20 = Cw20Base::new("another-cw20", mock.clone()); + another_cw20.upload().unwrap(); + another_cw20 + .instantiate( + &abstract_cw20_base::msg::InstantiateMsg { name: "DAO".to_string(), symbol: "DAO".to_string(), decimals: 6, @@ -2087,532 +1523,345 @@ fn test_cw20_receive_no_auto_add() { mint: None, marketing: None, }, - &[], - "another-token", + None, 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(); + let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); + voting.set_address(&gov.voting_module().unwrap()); + + let gov_token = Cw20Base::new("cw20", mock.clone()); + gov_token.set_address(&voting.token_contract().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, - }, + gov_token + .send( + Uint128::new(1), + gov.address().unwrap().to_string(), + to_json_binary(&"").unwrap(), ) .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()], - }, - &[], + + assert_eq!( + gov.cw_20_token_list(None, None).unwrap(), + Vec::::new() + ); + + gov.update_cw_20_list( + vec![ + another_cw20.address().unwrap().to_string(), + gov_token.address().unwrap().to_string(), + ], + vec![mock.addr_make("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]); + assert_eq!( + gov.cw_20_token_list(None, None).unwrap(), + vec![ + gov_token.address().unwrap(), + another_cw20.address().unwrap(), + ] + ); } #[test] fn test_cw721_receive() { - let (gov_addr, mut app) = do_standard_instantiate(true, None); - - let cw721_id = app.store_code(cw721_contract()); + let (gov, _proposal, mock, _) = do_standard_instantiate(true, false); - let cw721_addr = app - .instantiate_contract( - cw721_id, - Addr::unchecked(CREATOR_ADDR), + let cw721 = Cw721Base::new("cw721", mock.clone()); + cw721.upload().unwrap(); + cw721 + .instantiate( &cw721_base::msg::InstantiateMsg { name: "ekez".to_string(), symbol: "ekez".to_string(), - minter: CREATOR_ADDR.to_string(), + minter: mock.sender().to_string(), }, - &[], - "cw721", + None, None, ) .unwrap(); - let another_cw721 = app - .instantiate_contract( - cw721_id, - Addr::unchecked(CREATOR_ADDR), + let another_cw721 = Cw721Base::new("another_cw721", mock.clone()); + another_cw721.set_code_id(cw721.code_id().unwrap()); + another_cw721 + .instantiate( &cw721_base::msg::InstantiateMsg { name: "ekez".to_string(), symbol: "ekez".to_string(), - minter: CREATOR_ADDR.to_string(), + minter: mock.sender().to_string(), }, - &[], - "cw721", + None, 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, + cw721 + .execute( + &cw721_base::msg::ExecuteMsg::, Empty>::Mint { + token_id: "ekez".to_string(), + owner: mock.sender().to_string(), + token_uri: None, + extension: None, }, + 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()], + cw721 + .execute( + &cw721_base::msg::ExecuteMsg::, Empty>::SendNft { + contract: gov.address().unwrap().to_string(), + token_id: "ekez".to_string(), + msg: to_json_binary("").unwrap(), }, - &[], + None, ) - .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()], - }, - &[], + assert_eq!( + gov.cw_721_token_list(None, None).unwrap(), + vec![cw721.address().unwrap().clone()] + ); + + // Try to add an invalid cw721. + let err = gov + .update_cw_721_list( + vec![ + mock.addr_make("new").to_string(), + cw721.address().unwrap().clone().to_string(), + ], + vec![cw721.address().unwrap().clone().to_string()], ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!(err, ContractError::Unauthorized {})); + .unwrap_err(); + + println!("{:?}", err); + assert_contains(&err, "key:"); + assert_contains(err, "not found"); + // assert!(matches!(err, ContractError::Std(_))); + + // Test that non-DAO can not update the list. + let err = gov + .call_as(&mock.addr_make("ekez")) + .update_cw_721_list(vec![], vec![cw721.address().unwrap().clone().to_string()]) + .unwrap_err(); + + assert_contains(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()], - }, - &[], + gov.update_cw_721_list( + vec![ + cw721.address().unwrap().to_string(), + another_cw721.address().unwrap().to_string(), + ], + vec![cw721.address().unwrap().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]); + assert_eq!( + gov.cw_721_token_list(None, None).unwrap(), + vec![another_cw721.address().unwrap()] + ); } #[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 (gov, _proposal, mock, _) = do_standard_instantiate(false, false); - let cw721_addr = app - .instantiate_contract( - cw721_id, - Addr::unchecked(CREATOR_ADDR), + let cw721 = Cw721Base::new("cw721", mock.clone()); + cw721.upload().unwrap(); + cw721 + .instantiate( &cw721_base::msg::InstantiateMsg { name: "ekez".to_string(), symbol: "ekez".to_string(), - minter: CREATOR_ADDR.to_string(), + minter: mock.sender().to_string(), }, - &[], - "cw721", + None, None, ) .unwrap(); - let another_cw721 = app - .instantiate_contract( - cw721_id, - Addr::unchecked(CREATOR_ADDR), + let another_cw721 = Cw721Base::new("another_cw721", mock.clone()); + another_cw721.set_code_id(cw721.code_id().unwrap()); + another_cw721 + .instantiate( &cw721_base::msg::InstantiateMsg { name: "ekez".to_string(), symbol: "ekez".to_string(), - minter: CREATOR_ADDR.to_string(), + minter: mock.sender().to_string(), }, - &[], - "cw721", + None, 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()); + assert_eq!( + gov.cw_721_token_list(None, None).unwrap(), + 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![], - }, - &[], + gov.update_cw_721_list( + vec![ + another_cw721.address().unwrap().to_string(), + cw721.address().unwrap().to_string(), + cw721.address().unwrap().to_string(), + ], + 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]); + assert_eq!( + gov.cw_721_token_list(None, None).unwrap(), + vec![another_cw721.address().unwrap(), cw721.address().unwrap()] + ); } #[test] fn test_pause() { - let (core_addr, mut app) = do_standard_instantiate(false, None); + let (gov, _proposal, mock, _) = do_standard_instantiate(false, false); - 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(); + let start_height = mock.block_info().unwrap().height; + let proposal_modules = gov.proposal_modules(None, 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 {}); + assert_eq!(gov.pause_info().unwrap(), PauseInfoResponse::Unpaused {}); + + assert_eq!( + gov.dump_state().unwrap().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, - }, - }, - &[], - ) + gov.update_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(); + let err = gov + .call_as(&proposal_module.address) + .pause(Duration::Height(10)) + .unwrap_err(); // 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 {}) + assert_contains(err, ContractError::Unauthorized {}); + gov.call_as(&proposal_module.address) + .execute_proposal_hook(vec![WasmMsg::Execute { + contract_addr: gov.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) .unwrap(); + assert_eq!( - paused, + gov.pause_info().unwrap(), 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, + gov.dump_state().unwrap().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(); + gov.update_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, + }) + .unwrap(); + + let err = gov + .call_as(&proposal_module.address) + .execute_proposal_hook(vec![WasmMsg::Execute { + contract_addr: gov.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .unwrap_err(); - assert!(matches!(err, ContractError::Paused { .. })); + assert_contains(err, ContractError::Paused {}); - app.update_block(|block| block.height += 9); + mock.wait_blocks(9).unwrap(); // 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 { .. })); + let err = gov + .call_as(&proposal_module.address) + .execute_proposal_hook(vec![WasmMsg::Execute { + contract_addr: gov.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) + .unwrap_err(); + + assert_contains(err, ContractError::Paused {}); - app.update_block(|block| block.height += 1); + mock.wait_blocks(1).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 {}); + assert_eq!(gov.pause_info().unwrap(), PauseInfoResponse::Unpaused {}); + assert_eq!( + gov.dump_state().unwrap().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 {}) + gov.call_as(&proposal_module.address) + .execute_proposal_hook(vec![WasmMsg::Execute { + contract_addr: gov.address().unwrap().to_string(), + msg: to_json_binary(&ExecuteMsg::Pause { + duration: Duration::Height(10), + }) + .unwrap(), + funds: vec![], + } + .into()]) .unwrap(); + assert_eq!( - paused, + gov.pause_info().unwrap(), 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, + gov.dump_state().unwrap().pause_info, PauseInfoResponse::Paused { expiration: Expiration::AtHeight(start_height + 20) } @@ -2621,25 +1870,13 @@ fn test_pause() { #[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(); + let (gov, _proposal, _mock, _) = do_standard_instantiate(false, false); + let proposal_modules = gov.proposal_modules(None, 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(); + let all_state: DumpStateResponse = gov.dump_state().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); @@ -2650,24 +1887,29 @@ fn test_dump_state_proposal_modules() { // 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 mock = MockBech32::new("mock"); + let govmod = DaoProposalSudo::new("proposal", mock.clone()); + let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); + let gov = DaoDaoCore::new("dao-core", mock.clone()); + let cw20 = Cw20Base::new("cw20", mock.clone()); + + govmod.upload().unwrap(); + voting.upload().unwrap(); + gov.upload().unwrap(); + cw20.upload().unwrap(); let govmod_instantiate = dao_proposal_sudo::msg::InstantiateMsg { - root: CREATOR_ADDR.to_string(), + root: mock.sender().to_string(), }; let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { token_info: dao_voting_cw20_balance::msg::TokenInfo::New { - code_id: cw20_id, + code_id: cw20.code_id().unwrap(), 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(), + address: mock.sender().to_string(), amount: Uint128::from(2u64), }], marketing: None, @@ -2684,14 +1926,14 @@ fn test_migrate_from_compatible() { automatically_add_cw20s: false, automatically_add_cw721s: false, voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: voting_id, + code_id: voting.code_id().unwrap(), 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, + code_id: govmod.code_id().unwrap(), msg: to_json_binary(&govmod_instantiate).unwrap(), admin: Some(Admin::CoreModule {}), funds: vec![], @@ -2700,37 +1942,16 @@ fn test_migrate_from_compatible() { 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 {}) + gov.instantiate(&gov_instantiate, Some(&mock.sender()), None) .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 state = gov.dump_state().unwrap(); - let new_state: DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &QueryMsg::DumpState {}) + gov.migrate(&MigrateMsg::FromCompatible {}, gov.code_id().unwrap()) .unwrap(); + let new_state = gov.dump_state().unwrap(); + assert_eq!(new_state, state); } @@ -2738,25 +1959,31 @@ fn test_migrate_from_compatible() { 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 mock = MockBech32::new("mock"); + let govmod = DaoProposalSudo::new("proposal", mock.clone()); + let voting = DaoVotingCw20Balance::new("dao-voting", mock.clone()); + let gov = DaoDaoCore::new("dao-core", mock.clone()); + let v1_gov = DaoDaoCoreV1::new("dao-core-v1", mock.clone()); + let cw20 = Cw20Base::new("cw20", mock.clone()); + + govmod.upload().unwrap(); + voting.upload().unwrap(); + gov.upload().unwrap(); + v1_gov.upload().unwrap(); + cw20.upload().unwrap(); let proposal_instantiate = dao_proposal_sudo::msg::InstantiateMsg { - root: CREATOR_ADDR.to_string(), + root: mock.sender().to_string(), }; let voting_instantiate = dao_voting_cw20_balance::msg::InstantiateMsg { token_info: dao_voting_cw20_balance::msg::TokenInfo::New { - code_id: cw20_id, + code_id: cw20.code_id().unwrap(), 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(), + address: mock.sender().to_string(), amount: Uint128::from(2u64), }], marketing: None, @@ -2772,20 +1999,20 @@ fn test_migrate_from_beta() { automatically_add_cw20s: false, automatically_add_cw721s: false, voting_module_instantiate_info: v1::msg::ModuleInstantiateInfo { - code_id: voting_id, + code_id: voting.code_id().unwrap(), 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, + code_id: govmod.code_id().unwrap(), 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, + code_id: govmod.code_id().unwrap(), msg: to_json_binary(&proposal_instantiate).unwrap(), admin: v1::msg::Admin::CoreContract {}, label: "governance module 2".to_string(), @@ -2794,35 +2021,21 @@ fn test_migrate_from_beta() { 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()), - ) + v1_gov + .instantiate(&v1_core_instantiate, Some(&mock.sender()), None) .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(), - }), + gov.set_address(&v1_gov.address().unwrap()); + gov.migrate( + &MigrateMsg::FromV1 { + dao_uri: None, + params: None, + }, + gov.code_id().unwrap(), ) .unwrap(); - let new_state: DumpStateResponse = app - .wrap() - .query_wasm_smart(&core_addr, &QueryMsg::DumpState {}) - .unwrap(); + let new_state = gov.dump_state().unwrap(); let proposal_modules = new_state.proposal_modules; assert_eq!(2, proposal_modules.len()); @@ -2833,23 +2046,17 @@ fn test_migrate_from_beta() { } // 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(), - }), + let err = gov + .migrate( + &MigrateMsg::FromV1 { + dao_uri: None, + params: None, + }, + gov.code_id().unwrap(), ) - .unwrap_err() - .downcast() - .unwrap(); - assert_eq!(err, ContractError::AlreadyMigrated {}) + .unwrap_err(); + + assert_contains(err, ContractError::AlreadyMigrated {}) } #[test] @@ -2920,44 +2127,34 @@ fn test_migrate_mock() { #[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(); + let (gov, _proposal, _mock, _) = do_standard_instantiate(true, false); + let proposal_modules = gov.proposal_modules(None, 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(), - }], - }, - &[], - ); + let res = gov + .call_as(&proposal_module.address) + .execute_proposal_hook(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 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: CREATOR_ADDR.to_string(), + root: mock.sender().to_string(), }; let gov_instantiate = InstantiateMsg { @@ -3001,28 +2198,9 @@ fn test_module_prefixes() { 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(); + gov.instantiate(&gov_instantiate, None, None).unwrap(); + let modules = gov.proposal_modules(None, None).unwrap(); assert_eq!(modules.len(), 3); let module_1 = &modules[0]; @@ -3032,26 +2210,17 @@ fn test_module_prefixes() { let module_2 = &modules[1]; assert_eq!(module_2.status, ProposalModuleStatus::Enabled {}); - assert_eq!(module_2.prefix, "B"); + assert_eq!(module_2.prefix, "C"); 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.prefix, "B"); assert_eq!(&module_3.address, &modules[2].address); } -fn get_active_modules(app: &App, gov_addr: Addr) -> Vec { - let modules: Vec = app - .wrap() - .query_wasm_smart( - gov_addr, - &QueryMsg::ProposalModules { - start_after: None, - limit: None, - }, - ) - .unwrap(); +fn get_active_modules(gov: &DaoDaoCore) -> Vec { + let modules = gov.proposal_modules(None, None).unwrap(); modules .into_iter() @@ -3059,19 +2228,12 @@ fn get_active_modules(app: &App, gov_addr: Addr) -> Vec { .collect() } -fn query_proposal_module_count(app: &App, core_addr: &Addr) -> ProposalModuleCountResponse { - app.wrap() - .query_wasm_smart(core_addr, &QueryMsg::ProposalModuleCount {}) - .unwrap() -} - #[test] fn test_add_remove_subdaos() { - let (core_addr, mut app) = do_standard_instantiate(false, None); + let (gov, _proposal, mock, _) = do_standard_instantiate(false, false); test_unauthorized( - &mut app, - core_addr.clone(), + &gov.call_as(&mock.sender()), ExecuteMsg::UpdateSubDaos { to_add: vec![], to_remove: vec![], @@ -3080,89 +2242,46 @@ fn test_add_remove_subdaos() { let to_add: Vec = vec![ SubDao { - addr: "subdao001".to_string(), + addr: mock.addr_make("subdao001").to_string(), charter: None, }, SubDao { - addr: "subdao002".to_string(), + addr: mock.addr_make("subdao002").to_string(), charter: Some("cool charter bro".to_string()), }, SubDao { - addr: "subdao005".to_string(), + addr: mock.addr_make("subdao005").to_string(), charter: None, }, SubDao { - addr: "subdao007".to_string(), + addr: mock.addr_make("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(); + gov.update_sub_daos(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!(gov.list_sub_daos(None, None).unwrap().len(), 4); - assert_eq!(res.len(), 4); + let to_remove: Vec = vec![mock.addr_make("subdao005").to_string()]; - let to_remove: Vec = vec!["subdao005".to_string()]; + gov.update_sub_daos(vec![], to_remove).unwrap(); - 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(); + let res = gov.list_sub_daos(None, 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(), + addr: mock.addr_make("subdao001").to_string(), charter: None, }, SubDao { - addr: "subdao002".to_string(), + addr: mock.addr_make("subdao002").to_string(), charter: Some("cool charter bro".to_string()), }, SubDao { - addr: "subdao007".to_string(), + addr: mock.addr_make("subdao007").to_string(), charter: None, }, ]; @@ -3182,13 +2301,9 @@ pub fn test_migrate_update_version() { #[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(); + let (gov, _, _, _) = do_standard_instantiate(true, false); assert_eq!( - res, + gov.info().unwrap(), InfoResponse { info: ContractVersion { contract: CONTRACT_NAME.to_string(), From f7200010054133c391813143e181eb331a4fe9d8 Mon Sep 17 00:00:00 2001 From: Kayanski Date: Fri, 17 May 2024 09:06:45 +0000 Subject: [PATCH 7/8] Added derive fns --- Cargo.lock | 9 +++++++++ Cargo.toml | 1 + contracts/dao-dao-core/Cargo.toml | 2 +- contracts/proposal/dao-proposal-condorcet/Cargo.toml | 3 ++- contracts/proposal/dao-proposal-condorcet/src/msg.rs | 3 ++- contracts/proposal/dao-proposal-multiple/Cargo.toml | 1 + contracts/proposal/dao-proposal-multiple/src/msg.rs | 3 ++- contracts/proposal/dao-proposal-single/Cargo.toml | 1 + contracts/proposal/dao-proposal-single/src/msg.rs | 3 ++- contracts/staking/cw20-stake-external-rewards/Cargo.toml | 5 +++-- contracts/staking/cw20-stake-external-rewards/src/msg.rs | 3 ++- .../staking/cw20-stake-reward-distributor/Cargo.toml | 9 ++++++--- .../staking/cw20-stake-reward-distributor/src/msg.rs | 3 ++- contracts/staking/cw20-stake/Cargo.toml | 3 ++- contracts/staking/cw20-stake/src/msg.rs | 3 ++- contracts/test/dao-proposal-hook-counter/Cargo.toml | 2 +- contracts/test/dao-proposal-hook-counter/src/msg.rs | 3 ++- contracts/test/dao-proposal-sudo/Cargo.toml | 2 +- contracts/test/dao-test-custom-factory/Cargo.toml | 2 +- contracts/test/dao-test-custom-factory/src/msg.rs | 3 ++- contracts/test/dao-voting-cw20-balance/Cargo.toml | 4 ++-- packages/dao-interface/Cargo.toml | 2 +- packages/dao-pre-propose-base/Cargo.toml | 1 + packages/dao-pre-propose-base/src/msg.rs | 3 ++- 24 files changed, 51 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ce9be36c..eb089fd7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1700,6 +1700,7 @@ dependencies = [ "cw-controllers 1.1.2", "cw-hooks", "cw-multi-test", + "cw-orch", "cw-ownable", "cw-paginate-storage 2.4.2", "cw-storage-plus 1.2.0", @@ -1723,6 +1724,7 @@ dependencies = [ "cosmwasm-std", "cw-controllers 1.1.2", "cw-multi-test", + "cw-orch", "cw-ownable", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -1743,6 +1745,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", + "cw-orch", "cw-ownable", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -2178,6 +2181,7 @@ dependencies = [ "cw-denom", "cw-hooks", "cw-multi-test", + "cw-orch", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2244,6 +2248,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", + "cw-orch", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2266,6 +2271,7 @@ dependencies = [ "cosmwasm-std", "cw-hooks", "cw-multi-test", + "cw-orch", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2290,6 +2296,7 @@ dependencies = [ "cw-denom", "cw-hooks", "cw-multi-test", + "cw-orch", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2327,6 +2334,7 @@ dependencies = [ "cw-denom", "cw-hooks", "cw-multi-test", + "cw-orch", "cw-proposal-single", "cw-storage-plus 1.2.0", "cw-utils 0.13.4", @@ -2377,6 +2385,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", + "cw-orch", "cw-ownable", "cw-storage-plus 1.2.0", "cw-tokenfactory-issuer", diff --git a/Cargo.toml b/Cargo.toml index cf72e327a..c8cdffad4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,7 @@ syn = { version = "1.0", features = ["derive"] } test-context = "0.1" thiserror = { version = "1.0" } wynd-utils = "0.4" +cw-orch = "0.22.2" # One commit ahead of version 0.3.0. Allows initialization with an # optional owner. diff --git a/contracts/dao-dao-core/Cargo.toml b/contracts/dao-dao-core/Cargo.toml index eee9f8938..78c048ac2 100644 --- a/contracts/dao-dao-core/Cargo.toml +++ b/contracts/dao-dao-core/Cargo.toml @@ -35,7 +35,7 @@ abstract-cw-plus-interface = "2.0.1" abstract-cw20 = "2.0.0" abstract-cw20-base = "2.0.0" cw-multi-test = { workspace = true, features = ["stargate"] } -cw-orch = "0.22.2" +cw-orch = {workspace = true} cw20-base = { workspace = true } cw721-base = { workspace = true } dao-cw-orch = { path = "../../packages/cw-orch" } diff --git a/contracts/proposal/dao-proposal-condorcet/Cargo.toml b/contracts/proposal/dao-proposal-condorcet/Cargo.toml index a53910b27..a9326a4c6 100644 --- a/contracts/proposal/dao-proposal-condorcet/Cargo.toml +++ b/contracts/proposal/dao-proposal-condorcet/Cargo.toml @@ -1,5 +1,5 @@ [package] -name ="dao-proposal-condorcet" +name = "dao-proposal-condorcet" authors = ["ekez "] description = "A DAO DAO proposal module with ranked-choice, Condorcet voting." edition = { workspace = true } @@ -26,6 +26,7 @@ dao-voting = { workspace = true } dao-dao-macros = { workspace = true } dao-interface = { workspace = true } thiserror = { workspace = true } +cw-orch = { workspace = true } [dev-dependencies] cosmwasm-schema = { workspace = true } diff --git a/contracts/proposal/dao-proposal-condorcet/src/msg.rs b/contracts/proposal/dao-proposal-condorcet/src/msg.rs index ab6f5aa12..684108444 100644 --- a/contracts/proposal/dao-proposal-condorcet/src/msg.rs +++ b/contracts/proposal/dao-proposal-condorcet/src/msg.rs @@ -13,6 +13,7 @@ pub struct Choice { } #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { Propose { choices: Vec }, Vote { proposal_id: u32, vote: Vec }, @@ -23,7 +24,7 @@ pub enum ExecuteMsg { #[proposal_module_query] #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg { #[returns(crate::proposal::ProposalResponse)] Proposal { id: u32 }, diff --git a/contracts/proposal/dao-proposal-multiple/Cargo.toml b/contracts/proposal/dao-proposal-multiple/Cargo.toml index 5eca7e2df..e9d1f90c2 100644 --- a/contracts/proposal/dao-proposal-multiple/Cargo.toml +++ b/contracts/proposal/dao-proposal-multiple/Cargo.toml @@ -39,6 +39,7 @@ cw-hooks = { workspace = true } dao-hooks = { workspace = true } dao-pre-propose-multiple = { workspace = true } voting-v1 = { workspace = true } +cw-orch = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/contracts/proposal/dao-proposal-multiple/src/msg.rs b/contracts/proposal/dao-proposal-multiple/src/msg.rs index 1ed54db93..071e8d802 100644 --- a/contracts/proposal/dao-proposal-multiple/src/msg.rs +++ b/contracts/proposal/dao-proposal-multiple/src/msg.rs @@ -48,6 +48,7 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { /// Creates a proposal in the governance module. Propose(MultipleChoiceProposeMsg), @@ -147,7 +148,7 @@ pub enum ExecuteMsg { #[proposal_module_query] #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg { /// Gets the governance module's config. #[returns(crate::state::Config)] diff --git a/contracts/proposal/dao-proposal-single/Cargo.toml b/contracts/proposal/dao-proposal-single/Cargo.toml index 06170dd00..6f694fe09 100644 --- a/contracts/proposal/dao-proposal-single/Cargo.toml +++ b/contracts/proposal/dao-proposal-single/Cargo.toml @@ -34,6 +34,7 @@ thiserror = { workspace = true } cw-utils-v1 = { workspace = true} voting-v1 = { workspace = true } cw-proposal-single-v1 = { workspace = true, features = ["library"] } +cw-orch = {workspace = true} [dev-dependencies] anyhow = { workspace = true } diff --git a/contracts/proposal/dao-proposal-single/src/msg.rs b/contracts/proposal/dao-proposal-single/src/msg.rs index 302303b48..82e75f22f 100644 --- a/contracts/proposal/dao-proposal-single/src/msg.rs +++ b/contracts/proposal/dao-proposal-single/src/msg.rs @@ -47,6 +47,7 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { /// Creates a proposal in the module. Propose(SingleChoiceProposeMsg), @@ -147,7 +148,7 @@ pub enum ExecuteMsg { #[proposal_module_query] #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg { /// Gets the proposal module's config. #[returns(crate::state::Config)] diff --git a/contracts/staking/cw20-stake-external-rewards/Cargo.toml b/contracts/staking/cw20-stake-external-rewards/Cargo.toml index 9f59c261e..f081eb63a 100644 --- a/contracts/staking/cw20-stake-external-rewards/Cargo.toml +++ b/contracts/staking/cw20-stake-external-rewards/Cargo.toml @@ -22,15 +22,16 @@ cw-storage-plus = { workspace = true } cw-controllers = { workspace = true } cw20 = { workspace = true } cw-utils = { workspace = true } -cw20-base = { workspace = true, features = ["library"] } +cw20-base = { workspace = true, features = ["library"] } cw2 = { workspace = true } thiserror = { workspace = true } -cw20-stake = { workspace = true, features = ["library"]} +cw20-stake = { workspace = true, features = ["library"] } cw-ownable = { workspace = true } dao-hooks = { workspace = true } cw20-stake-external-rewards-v1 = { workspace = true } cw20-013 = { package = "cw20", version = "0.13" } +cw-orch = { workspace = true } [dev-dependencies] cw-multi-test = { workspace = true } diff --git a/contracts/staking/cw20-stake-external-rewards/src/msg.rs b/contracts/staking/cw20-stake-external-rewards/src/msg.rs index eb873c798..e4bc3be81 100644 --- a/contracts/staking/cw20-stake-external-rewards/src/msg.rs +++ b/contracts/staking/cw20-stake-external-rewards/src/msg.rs @@ -22,6 +22,7 @@ pub struct InstantiateMsg { #[cw_ownable_execute] #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { StakeChangeHook(StakeChangedHookMsg), Claim {}, @@ -45,7 +46,7 @@ pub enum ReceiveMsg { } #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg { #[returns(InfoResponse)] Info {}, diff --git a/contracts/staking/cw20-stake-reward-distributor/Cargo.toml b/contracts/staking/cw20-stake-reward-distributor/Cargo.toml index 75972d965..5ecfcd971 100644 --- a/contracts/staking/cw20-stake-reward-distributor/Cargo.toml +++ b/contracts/staking/cw20-stake-reward-distributor/Cargo.toml @@ -1,7 +1,9 @@ [package] name = "cw20-stake-reward-distributor" edition = "2018" -authors = ["Vernon Johnson , ekez "] +authors = [ + "Vernon Johnson , ekez ", +] description = "Distributes cw20 staking rewards." license = { workspace = true } repository = { workspace = true } @@ -24,11 +26,12 @@ cw-storage-plus = { workspace = true } cw2 = { workspace = true } cw20 = { workspace = true } cw-utils = { workspace = true } -cw20-base = { workspace = true, features = ["library"] } -cw20-stake = { workspace = true, features = ["library"]} +cw20-base = { workspace = true, features = ["library"] } +cw20-stake = { workspace = true, features = ["library"] } thiserror = { workspace = true } cw-ownable = { workspace = true } cw20-stake-reward-distributor-v1 = { workspace = true, features = ["library"] } +cw-orch = { workspace = true } [dev-dependencies] cw-multi-test = { workspace = true } diff --git a/contracts/staking/cw20-stake-reward-distributor/src/msg.rs b/contracts/staking/cw20-stake-reward-distributor/src/msg.rs index 47e347086..a54ca3c5f 100644 --- a/contracts/staking/cw20-stake-reward-distributor/src/msg.rs +++ b/contracts/staking/cw20-stake-reward-distributor/src/msg.rs @@ -18,6 +18,7 @@ pub struct InstantiateMsg { #[cw_ownable_execute] #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { UpdateConfig { staking_addr: String, @@ -29,7 +30,7 @@ pub enum ExecuteMsg { } #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg { #[returns(InfoResponse)] Info {}, diff --git a/contracts/staking/cw20-stake/Cargo.toml b/contracts/staking/cw20-stake/Cargo.toml index 4417b372b..47cf154d0 100644 --- a/contracts/staking/cw20-stake/Cargo.toml +++ b/contracts/staking/cw20-stake/Cargo.toml @@ -23,7 +23,7 @@ cw-controllers = { workspace = true } cw-hooks = { workspace = true } cw20 = { workspace = true } cw-utils = { workspace = true } -cw20-base = { workspace = true, features = ["library"] } +cw20-base = { workspace = true, features = ["library"] } cw2 = { workspace = true } thiserror = { workspace = true } cw-paginate-storage = { workspace = true } @@ -33,6 +33,7 @@ dao-voting = { workspace = true } cw20-stake-v1 = { workspace = true, features = ["library"] } cw-utils-v1 = { workspace = true } +cw-orch = { workspace = true } [dev-dependencies] cw-multi-test = { workspace = true } diff --git a/contracts/staking/cw20-stake/src/msg.rs b/contracts/staking/cw20-stake/src/msg.rs index bfe65466b..19dd36f71 100644 --- a/contracts/staking/cw20-stake/src/msg.rs +++ b/contracts/staking/cw20-stake/src/msg.rs @@ -21,6 +21,7 @@ pub struct InstantiateMsg { #[cw_ownable_execute] #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { Receive(Cw20ReceiveMsg), Unstake { amount: Uint128 }, @@ -37,7 +38,7 @@ pub enum ReceiveMsg { } #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg { #[returns(StakedBalanceAtHeightResponse)] StakedBalanceAtHeight { diff --git a/contracts/test/dao-proposal-hook-counter/Cargo.toml b/contracts/test/dao-proposal-hook-counter/Cargo.toml index 395fb338a..00606da08 100644 --- a/contracts/test/dao-proposal-hook-counter/Cargo.toml +++ b/contracts/test/dao-proposal-hook-counter/Cargo.toml @@ -23,7 +23,7 @@ cw-storage-plus = { workspace = true } cw2 = { workspace = true } thiserror = { workspace = true } dao-hooks = { workspace = true } - +cw-orch = { workspace = true } [dev-dependencies] cw-hooks = { workspace = true } cw20 = { workspace = true } diff --git a/contracts/test/dao-proposal-hook-counter/src/msg.rs b/contracts/test/dao-proposal-hook-counter/src/msg.rs index ad6048228..9a4a6f5e6 100644 --- a/contracts/test/dao-proposal-hook-counter/src/msg.rs +++ b/contracts/test/dao-proposal-hook-counter/src/msg.rs @@ -8,6 +8,7 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { ProposalHook(ProposalHookMsg), StakeChangeHook(StakeChangedHookMsg), @@ -15,7 +16,7 @@ pub enum ExecuteMsg { } #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg { #[returns(Uint128)] StakeCounter {}, diff --git a/contracts/test/dao-proposal-sudo/Cargo.toml b/contracts/test/dao-proposal-sudo/Cargo.toml index 87f102efc..cb4fa9f46 100644 --- a/contracts/test/dao-proposal-sudo/Cargo.toml +++ b/contracts/test/dao-proposal-sudo/Cargo.toml @@ -24,7 +24,7 @@ cw2 = { workspace = true } thiserror = { workspace = true } dao-dao-macros = { workspace = true } dao-interface = { workspace = true } -cw-orch = "0.22.2" +cw-orch = { workspace = true } [dev-dependencies] cw-multi-test = { workspace = true } diff --git a/contracts/test/dao-test-custom-factory/Cargo.toml b/contracts/test/dao-test-custom-factory/Cargo.toml index 1c7a8cb5c..e25fe5a54 100644 --- a/contracts/test/dao-test-custom-factory/Cargo.toml +++ b/contracts/test/dao-test-custom-factory/Cargo.toml @@ -33,6 +33,6 @@ cw-tokenfactory-issuer = { workspace = true, features = [ "library", "osmosis_tokenfactory", ] } - +cw-orch = { workspace = true } [dev-dependencies] cw-multi-test = { workspace = true } diff --git a/contracts/test/dao-test-custom-factory/src/msg.rs b/contracts/test/dao-test-custom-factory/src/msg.rs index 42128cd2e..7e875d0d8 100644 --- a/contracts/test/dao-test-custom-factory/src/msg.rs +++ b/contracts/test/dao-test-custom-factory/src/msg.rs @@ -7,6 +7,7 @@ use dao_interface::token::NewTokenInfo; pub struct InstantiateMsg {} #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { /// Example NFT factory implementation NftFactory { @@ -37,7 +38,7 @@ pub enum ExecuteMsg { } #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg { #[returns(dao_interface::voting::InfoResponse)] Info {}, diff --git a/contracts/test/dao-voting-cw20-balance/Cargo.toml b/contracts/test/dao-voting-cw20-balance/Cargo.toml index 77e55bf88..f750ba315 100644 --- a/contracts/test/dao-voting-cw20-balance/Cargo.toml +++ b/contracts/test/dao-voting-cw20-balance/Cargo.toml @@ -26,8 +26,8 @@ cw-utils = { workspace = true } thiserror = { workspace = true } dao-dao-macros = { workspace = true } dao-interface = { workspace = true } -cw20-base = { workspace = true, features = ["library"] } -cw-orch = "0.22.2" +cw20-base = { workspace = true, features = ["library"] } +cw-orch = { workspace = true } [dev-dependencies] cw-multi-test = { workspace = true } diff --git a/packages/dao-interface/Cargo.toml b/packages/dao-interface/Cargo.toml index 62252d894..6cf745ab2 100644 --- a/packages/dao-interface/Cargo.toml +++ b/packages/dao-interface/Cargo.toml @@ -15,7 +15,7 @@ cw20 = { workspace = true } cw721 = { workspace = true } cw-utils = { workspace = true } osmosis-std = { workspace = true } -cw-orch = "0.22.2" +cw-orch = {workspace = true} [dev-dependencies] cosmwasm-schema = { workspace = true } diff --git a/packages/dao-pre-propose-base/Cargo.toml b/packages/dao-pre-propose-base/Cargo.toml index c063d34f9..04a72aa5c 100644 --- a/packages/dao-pre-propose-base/Cargo.toml +++ b/packages/dao-pre-propose-base/Cargo.toml @@ -28,6 +28,7 @@ dao-interface = { workspace = true } dao-voting = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } +cw-orch = {workspace = true} [dev-dependencies] cw-multi-test = { workspace = true } diff --git a/packages/dao-pre-propose-base/src/msg.rs b/packages/dao-pre-propose-base/src/msg.rs index b2f2cc410..1ed26e8f6 100644 --- a/packages/dao-pre-propose-base/src/msg.rs +++ b/packages/dao-pre-propose-base/src/msg.rs @@ -20,6 +20,7 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { /// Creates a new proposal in the pre-propose module. MSG will be /// serialized and used as the proposal creation message. @@ -89,7 +90,7 @@ pub enum ExecuteMsg { } #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg where QueryExt: JsonSchema, From 8c945acdb0746ec84d15cfebeadcfe32122f85a2 Mon Sep 17 00:00:00 2001 From: Kayanski Date: Fri, 17 May 2024 09:09:32 +0000 Subject: [PATCH 8/8] Added other derives --- Cargo.lock | 5 +++++ contracts/voting/dao-voting-cw20-staked/Cargo.toml | 1 + contracts/voting/dao-voting-cw20-staked/src/msg.rs | 3 ++- contracts/voting/dao-voting-cw4/Cargo.toml | 1 + contracts/voting/dao-voting-cw4/src/msg.rs | 3 ++- contracts/voting/dao-voting-cw721-roles/Cargo.toml | 1 + contracts/voting/dao-voting-cw721-roles/src/msg.rs | 3 ++- contracts/voting/dao-voting-cw721-staked/Cargo.toml | 1 + contracts/voting/dao-voting-cw721-staked/src/msg.rs | 3 ++- contracts/voting/dao-voting-token-staked/Cargo.toml | 1 + contracts/voting/dao-voting-token-staked/src/msg.rs | 3 ++- 11 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eb089fd7b..2ab8a792b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2495,6 +2495,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", + "cw-orch", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2514,6 +2515,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", + "cw-orch", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2532,6 +2534,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", + "cw-orch", "cw-ownable", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -2558,6 +2561,7 @@ dependencies = [ "cw-controllers 1.1.2", "cw-hooks", "cw-multi-test", + "cw-orch", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2588,6 +2592,7 @@ dependencies = [ "cw-controllers 1.1.2", "cw-hooks", "cw-multi-test", + "cw-orch", "cw-ownable", "cw-storage-plus 1.2.0", "cw-tokenfactory-issuer", diff --git a/contracts/voting/dao-voting-cw20-staked/Cargo.toml b/contracts/voting/dao-voting-cw20-staked/Cargo.toml index 4eae84a64..6a9863868 100644 --- a/contracts/voting/dao-voting-cw20-staked/Cargo.toml +++ b/contracts/voting/dao-voting-cw20-staked/Cargo.toml @@ -29,6 +29,7 @@ thiserror = { workspace = true } dao-dao-macros = { workspace = true } dao-interface = { workspace = true } dao-voting = { workspace = true } +cw-orch.workspace = true [dev-dependencies] cw-multi-test = { workspace = true } diff --git a/contracts/voting/dao-voting-cw20-staked/src/msg.rs b/contracts/voting/dao-voting-cw20-staked/src/msg.rs index bdb5e9f2a..0031d0deb 100644 --- a/contracts/voting/dao-voting-cw20-staked/src/msg.rs +++ b/contracts/voting/dao-voting-cw20-staked/src/msg.rs @@ -61,6 +61,7 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { /// Sets the active threshold to a new value. Only the /// instantiator this contract (a DAO most likely) may call this @@ -74,7 +75,7 @@ pub enum ExecuteMsg { #[cw20_token_query] #[active_query] #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg { /// Gets the address of the cw20-stake contract this voting module /// is wrapping. diff --git a/contracts/voting/dao-voting-cw4/Cargo.toml b/contracts/voting/dao-voting-cw4/Cargo.toml index 55c671560..6ad83eeb7 100644 --- a/contracts/voting/dao-voting-cw4/Cargo.toml +++ b/contracts/voting/dao-voting-cw4/Cargo.toml @@ -27,6 +27,7 @@ dao-dao-macros = { workspace = true } dao-interface = { workspace = true } cw4 = { workspace = true } cw4-group = { workspace = true } +cw-orch.workspace = true [dev-dependencies] cw-multi-test = { workspace = true } diff --git a/contracts/voting/dao-voting-cw4/src/msg.rs b/contracts/voting/dao-voting-cw4/src/msg.rs index 24bd0eebc..8ae8cc6b9 100644 --- a/contracts/voting/dao-voting-cw4/src/msg.rs +++ b/contracts/voting/dao-voting-cw4/src/msg.rs @@ -18,11 +18,12 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg {} #[voting_module_query] #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg { #[returns(cosmwasm_std::Addr)] GroupContract {}, diff --git a/contracts/voting/dao-voting-cw721-roles/Cargo.toml b/contracts/voting/dao-voting-cw721-roles/Cargo.toml index e816d763c..3ad3128ea 100644 --- a/contracts/voting/dao-voting-cw721-roles/Cargo.toml +++ b/contracts/voting/dao-voting-cw721-roles/Cargo.toml @@ -29,6 +29,7 @@ cw-utils = { workspace = true } cw2 = { workspace = true } cw4 = { workspace = true } thiserror = { workspace = true } +cw-orch.workspace = true [dev-dependencies] cw-multi-test = { workspace = true } diff --git a/contracts/voting/dao-voting-cw721-roles/src/msg.rs b/contracts/voting/dao-voting-cw721-roles/src/msg.rs index b15099529..3a2314adf 100644 --- a/contracts/voting/dao-voting-cw721-roles/src/msg.rs +++ b/contracts/voting/dao-voting-cw721-roles/src/msg.rs @@ -44,11 +44,12 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg {} #[voting_module_query] #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg { #[returns(crate::state::Config)] Config {}, diff --git a/contracts/voting/dao-voting-cw721-staked/Cargo.toml b/contracts/voting/dao-voting-cw721-staked/Cargo.toml index af7d5532e..edb6f2a9f 100644 --- a/contracts/voting/dao-voting-cw721-staked/Cargo.toml +++ b/contracts/voting/dao-voting-cw721-staked/Cargo.toml @@ -38,6 +38,7 @@ dao-hooks = { workspace = true } dao-interface = { workspace = true } dao-voting = { workspace = true } thiserror = { workspace = true } +cw-orch.workspace = true [dev-dependencies] anyhow = { workspace = true } diff --git a/contracts/voting/dao-voting-cw721-staked/src/msg.rs b/contracts/voting/dao-voting-cw721-staked/src/msg.rs index 837851ed3..d318ddb48 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/msg.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/msg.rs @@ -45,6 +45,7 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { /// Used to stake NFTs. To stake a NFT send a cw721 send message /// to this contract with the NFT you would like to stake. The @@ -75,7 +76,7 @@ pub enum ExecuteMsg { #[active_query] #[voting_module_query] #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg { #[returns(crate::state::Config)] Config {}, diff --git a/contracts/voting/dao-voting-token-staked/Cargo.toml b/contracts/voting/dao-voting-token-staked/Cargo.toml index 8be6ca1f6..e32a0f4cc 100644 --- a/contracts/voting/dao-voting-token-staked/Cargo.toml +++ b/contracts/voting/dao-voting-token-staked/Cargo.toml @@ -48,6 +48,7 @@ dao-voting = { workspace = true } cw-tokenfactory-issuer = { workspace = true, default-features = false, features = [ "library", ] } +cw-orch.workspace = true [dev-dependencies] anyhow = { workspace = true } diff --git a/contracts/voting/dao-voting-token-staked/src/msg.rs b/contracts/voting/dao-voting-token-staked/src/msg.rs index 98809ec9e..6a71bcaf1 100644 --- a/contracts/voting/dao-voting-token-staked/src/msg.rs +++ b/contracts/voting/dao-voting-token-staked/src/msg.rs @@ -36,6 +36,7 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { /// Stakes tokens with the contract to get voting power in the DAO Stake {}, @@ -61,7 +62,7 @@ pub enum ExecuteMsg { #[active_query] #[voting_module_query] #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg { #[returns(crate::state::Config)] GetConfig {},