diff --git a/Cargo.lock b/Cargo.lock index 6a4bba49e..2ecb4fb8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,7 +43,7 @@ dependencies = [ "abstract-cw4-group", "abstract-cw4-stake", "cosmwasm-std", - "cw-orch", + "cw-orch 0.22.2", ] [[package]] @@ -69,7 +69,7 @@ dependencies = [ "abstract-cw2", "cosmwasm-schema", "cosmwasm-std", - "cw-orch", + "cw-orch 0.22.2", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "schemars", @@ -88,7 +88,7 @@ dependencies = [ "abstract-cw2", "cosmwasm-schema", "cosmwasm-std", - "cw-orch", + "cw-orch 0.22.2", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "schemars", @@ -118,7 +118,7 @@ checksum = "00d5e4b8084c3a2b3e42502e6c4fe3ed985dc72e86eb612bcc527f4a0443fa42" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-orch", + "cw-orch 0.22.2", "cw-utils 1.0.3", "schemars", "serde", @@ -134,7 +134,7 @@ dependencies = [ "abstract-cw20", "cosmwasm-schema", "cosmwasm-std", - "cw-orch", + "cw-orch 0.22.2", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "schemars", @@ -154,7 +154,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-controllers 1.1.2", - "cw-orch", + "cw-orch 0.22.2", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "schemars", @@ -188,7 +188,7 @@ dependencies = [ "abstract-cw3", "cosmwasm-schema", "cosmwasm-std", - "cw-orch", + "cw-orch 0.22.2", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "schemars", @@ -209,7 +209,7 @@ dependencies = [ "abstract-cw4", "cosmwasm-schema", "cosmwasm-std", - "cw-orch", + "cw-orch 0.22.2", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "schemars", @@ -241,7 +241,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-controllers 1.1.2", - "cw-orch", + "cw-orch 0.22.2", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "schemars", @@ -261,7 +261,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-controllers 1.1.2", - "cw-orch", + "cw-orch 0.22.2", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "schemars", @@ -653,9 +653,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324c74f2155653c90b04f25b2a47a8a631360cb908f92a772695f430c7e31052" +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" [[package]] name = "cexpr" @@ -1022,7 +1022,7 @@ dependencies = [ "cw-address-like", "cw-curves", "cw-multi-test", - "cw-orch", + "cw-orch 0.24.1", "cw-ownable", "cw-paginate-storage 2.5.0", "cw-storage-plus 1.2.0", @@ -1060,6 +1060,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", + "cw-orch 0.24.1", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -1262,9 +1263,30 @@ dependencies = [ "cosmwasm-std", "cw-orch-contract-derive", "cw-orch-core", - "cw-orch-fns-derive", - "cw-orch-mock", - "cw-orch-traits", + "cw-orch-fns-derive 0.19.1", + "cw-orch-mock 0.22.4", + "cw-orch-traits 0.22.0", + "cw-utils 1.0.3", + "hex", + "log", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw-orch" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c81cb500eb2f9be31a0f90c7ce66572ee4a790ffbae1c6b42ff2e3f9faf3479" +dependencies = [ + "anyhow", + "cosmwasm-std", + "cw-orch-contract-derive", + "cw-orch-core", + "cw-orch-fns-derive 0.22.0", + "cw-orch-mock 0.23.2", + "cw-orch-traits 0.23.1", "cw-utils 1.0.3", "hex", "log", @@ -1316,6 +1338,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "cw-orch-fns-derive" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e21b23116a0702f540d7fa3f16e8276682d860b589fed56259220ad59d768e" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "cw-orch-mock" version = "0.22.4" @@ -1331,6 +1365,21 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "cw-orch-mock" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57beb30d841bde79df51c9402741ef926ca8ef7ecd3570aa180074f767ac04d3" +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" @@ -1342,6 +1391,17 @@ dependencies = [ "prost-types 0.12.6", ] +[[package]] +name = "cw-orch-traits" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05bb98fc8d7d51c632ae217f5040edb91695351977778d6cb7ea3c3d4efa890" +dependencies = [ + "cw-orch-core", + "prost 0.12.6", + "prost-types 0.12.6", +] + [[package]] name = "cw-ownable" version = "0.5.1" @@ -1398,6 +1458,7 @@ dependencies = [ "cosmwasm-std", "cw-denom", "cw-multi-test", + "cw-orch 0.24.1", "cw-ownable", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -1494,6 +1555,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", + "cw-orch 0.24.1", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -1509,6 +1571,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", + "cw-orch 0.24.1", "cw-ownable", "cw-storage-plus 1.2.0", "cw-tokenfactory-types", @@ -1602,6 +1665,7 @@ dependencies = [ "cosmwasm-std", "cw-denom", "cw-multi-test", + "cw-orch 0.24.1", "cw-ownable", "cw-stake-tracker", "cw-storage-plus 1.2.0", @@ -1812,7 +1876,7 @@ dependencies = [ "cw-controllers 1.1.2", "cw-hooks", "cw-multi-test", - "cw-orch", + "cw-orch 0.24.1", "cw-ownable", "cw-paginate-storage 2.5.0", "cw-storage-plus 1.2.0", @@ -1836,7 +1900,7 @@ dependencies = [ "cosmwasm-std", "cw-controllers 1.1.2", "cw-multi-test", - "cw-orch", + "cw-orch 0.24.1", "cw-ownable", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -1857,7 +1921,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-orch", + "cw-orch 0.24.1", "cw-ownable", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -2074,6 +2138,7 @@ dependencies = [ "cosmwasm-std", "cw-controllers 1.1.2", "cw-multi-test", + "cw-orch 0.24.1", "cw-ownable", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -2121,13 +2186,20 @@ version = "2.5.0" dependencies = [ "cosmwasm-std", "cw-abc", - "cw-orch", + "cw-admin-factory", + "cw-orch 0.22.2", + "cw-payroll-factory", + "cw-token-swap", + "cw-tokenfactory-issuer", + "cw-vesting", "cw20-stake 2.5.0", "cw20-stake-external-rewards", "cw20-stake-reward-distributor", "cw721-base 0.18.0", + "cw721-roles", "dao-dao-core", "dao-interface", + "dao-migrator", "dao-pre-propose-approval-single", "dao-pre-propose-approver", "dao-pre-propose-multiple", @@ -2154,6 +2226,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-controllers 1.1.2", + "cw-orch 0.24.1", "cw4 1.1.2", ] @@ -2168,7 +2241,7 @@ dependencies = [ "cosmwasm-std", "cw-core", "cw-multi-test", - "cw-orch", + "cw-orch 0.24.1", "cw-paginate-storage 2.5.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -2217,7 +2290,7 @@ version = "2.5.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-orch", + "cw-orch 0.24.1", "cw-utils 1.0.3", "cw2 1.1.2", "cw20 1.1.2", @@ -2235,6 +2308,7 @@ dependencies = [ "cw-core", "cw-core-interface 0.1.0 (git+https://github.com/DA0-DA0/dao-contracts.git?tag=v1.0.0)", "cw-multi-test", + "cw-orch 0.24.1", "cw-proposal-single", "cw-storage-plus 1.2.0", "cw-utils 0.13.4", @@ -2321,7 +2395,7 @@ dependencies = [ "cw-denom", "cw-hooks", "cw-multi-test", - "cw-orch", + "cw-orch 0.24.1", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2388,7 +2462,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-orch", + "cw-orch 0.24.1", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2411,7 +2485,7 @@ dependencies = [ "cosmwasm-std", "cw-hooks", "cw-multi-test", - "cw-orch", + "cw-orch 0.24.1", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2436,7 +2510,7 @@ dependencies = [ "cw-denom", "cw-hooks", "cw-multi-test", - "cw-orch", + "cw-orch 0.24.1", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2474,7 +2548,7 @@ dependencies = [ "cw-denom", "cw-hooks", "cw-multi-test", - "cw-orch", + "cw-orch 0.24.1", "cw-proposal-single", "cw-storage-plus 1.2.0", "cw-utils 0.13.4", @@ -2510,7 +2584,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-orch", + "cw-orch 0.24.1", "cw-storage-plus 1.2.0", "cw2 1.1.2", "dao-dao-macros", @@ -2555,7 +2629,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-orch", + "cw-orch 0.24.1", "cw-ownable", "cw-storage-plus 1.2.0", "cw-tokenfactory-issuer", @@ -2650,7 +2724,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-orch", + "cw-orch 0.24.1", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2668,7 +2742,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-orch", + "cw-orch 0.24.1", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2688,7 +2762,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-orch", + "cw-orch 0.24.1", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2707,7 +2781,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-orch", + "cw-orch 0.24.1", "cw-ownable", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -2734,7 +2808,7 @@ dependencies = [ "cw-controllers 1.1.2", "cw-hooks", "cw-multi-test", - "cw-orch", + "cw-orch 0.24.1", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2794,7 +2868,7 @@ dependencies = [ "cw-controllers 1.1.2", "cw-hooks", "cw-multi-test", - "cw-orch", + "cw-orch 0.24.1", "cw-ownable", "cw-storage-plus 1.2.0", "cw-tokenfactory-issuer", diff --git a/Cargo.toml b/Cargo.toml index 129e116cb..c9ff1a26a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,7 +83,7 @@ syn = { version = "1.0", features = ["derive"] } test-context = "0.1" thiserror = { version = "1.0" } wynd-utils = "0.4" -cw-orch = "0.22.2" +cw-orch = "0.24.1" # One commit ahead of version 0.3.0. Allows initialization with an # optional owner. diff --git a/contracts/dao-dao-core/src/tests.rs b/contracts/dao-dao-core/src/tests.rs index e20eab146..acf3b0f33 100644 --- a/contracts/dao-dao-core/src/tests.rs +++ b/contracts/dao-dao-core/src/tests.rs @@ -1,2314 +1,2314 @@ -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, - 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() - } - } - ) -} +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, + 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_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)?, + 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_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: 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_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, + }; + + 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_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, + }; + + 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_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, + }; + + 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_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, + }; + + 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_addr().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_addr().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_addr().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_addr().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_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.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_addr().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_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.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_addr().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_addr().to_string(), + }, + None, + None, + ) + .unwrap(); + + cw721 + .execute( + &cw721_base::msg::ExecuteMsg::, Empty>::Mint { + token_id: "ekez".to_string(), + owner: mock.sender_addr().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_addr().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_addr().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_addr().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_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.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_addr()), 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_addr().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_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.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_addr()), 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_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, + }; + + 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_addr()), + 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/external/cw-admin-factory/Cargo.toml b/contracts/external/cw-admin-factory/Cargo.toml index f7f0034ea..e3c8416ef 100644 --- a/contracts/external/cw-admin-factory/Cargo.toml +++ b/contracts/external/cw-admin-factory/Cargo.toml @@ -21,6 +21,7 @@ cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } cw-storage-plus = { workspace = true } cw2 = { workspace = true } +cw-orch = { workspace = true } thiserror = { workspace = true } cw-utils = { workspace = true } diff --git a/contracts/external/cw-admin-factory/src/msg.rs b/contracts/external/cw-admin-factory/src/msg.rs index 35a472783..faee0dac2 100644 --- a/contracts/external/cw-admin-factory/src/msg.rs +++ b/contracts/external/cw-admin-factory/src/msg.rs @@ -9,6 +9,7 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { /// Instantiates the target contract with the provided instantiate message and code id and /// updates the contract's admin to be itself. @@ -20,7 +21,7 @@ pub enum ExecuteMsg { } #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg { #[returns(AdminResponse)] Admin {}, diff --git a/contracts/external/cw-payroll-factory/Cargo.toml b/contracts/external/cw-payroll-factory/Cargo.toml index 9b89969a5..efd300919 100644 --- a/contracts/external/cw-payroll-factory/Cargo.toml +++ b/contracts/external/cw-payroll-factory/Cargo.toml @@ -24,6 +24,7 @@ cw-ownable = { workspace = true } cw-storage-plus = { workspace = true } cw2 = { workspace = true } cw20 = { workspace = true } +cw-orch = { workspace = true } thiserror = { workspace = true } cw-vesting = { workspace = true, features = ["library"] } cw-utils = { workspace = true } diff --git a/contracts/external/cw-payroll-factory/src/msg.rs b/contracts/external/cw-payroll-factory/src/msg.rs index eacc5357e..aeff10187 100644 --- a/contracts/external/cw-payroll-factory/src/msg.rs +++ b/contracts/external/cw-payroll-factory/src/msg.rs @@ -11,6 +11,7 @@ pub struct InstantiateMsg { #[cw_ownable_execute] #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { /// Instantiates a new vesting contract that is funded by a cw20 token. Receive(Cw20ReceiveMsg), @@ -36,7 +37,7 @@ pub enum ReceiveMsg { } #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg { /// Returns list of all vesting payment contracts #[returns(Vec)] diff --git a/contracts/external/cw-token-swap/Cargo.toml b/contracts/external/cw-token-swap/Cargo.toml index e974d5453..d10660ac0 100644 --- a/contracts/external/cw-token-swap/Cargo.toml +++ b/contracts/external/cw-token-swap/Cargo.toml @@ -23,6 +23,7 @@ cw-storage-plus = { workspace = true } cw-utils = { workspace = true } cw2 = { workspace = true } cw20 = { workspace = true } +cw-orch = { workspace = true } thiserror = { workspace = true } [dev-dependencies] diff --git a/contracts/external/cw-token-swap/src/msg.rs b/contracts/external/cw-token-swap/src/msg.rs index 1c591d111..2b3dd6580 100644 --- a/contracts/external/cw-token-swap/src/msg.rs +++ b/contracts/external/cw-token-swap/src/msg.rs @@ -32,6 +32,7 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { /// Used to provide cw20 tokens to satisfy a funds promise. Receive(cw20::Cw20ReceiveMsg), @@ -43,7 +44,7 @@ pub enum ExecuteMsg { } #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg { // Gets the current status of the escrow transaction. #[returns(crate::msg::StatusResponse)] diff --git a/contracts/external/cw-tokenfactory-issuer/Cargo.toml b/contracts/external/cw-tokenfactory-issuer/Cargo.toml index fa1954055..d85a5d2a9 100644 --- a/contracts/external/cw-tokenfactory-issuer/Cargo.toml +++ b/contracts/external/cw-tokenfactory-issuer/Cargo.toml @@ -45,6 +45,7 @@ kujira_tokenfactory = ["cw-tokenfactory-types/kujira_tokenfactory"] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw2 = { workspace = true } +cw-orch = { workspace = true } cw-ownable = { workspace = true } cw-storage-plus = { workspace = true } cw-tokenfactory-types = { workspace = true, default-features = false } diff --git a/contracts/external/cw-tokenfactory-issuer/src/msg.rs b/contracts/external/cw-tokenfactory-issuer/src/msg.rs index d1be17f21..05f5ce01b 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/msg.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/msg.rs @@ -24,6 +24,7 @@ pub enum InstantiateMsg { /// State changing methods available to this smart contract. #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { /// Allow adds the target address to the allowlist to be able to send or receive tokens even if the token /// is frozen. Token Factory's BeforeSendHook listener must be set to this contract in order for this feature @@ -109,7 +110,7 @@ pub struct MigrateMsg {} /// Queries supported by this smart contract. #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg { /// Returns if token transfer is disabled. Response: IsFrozenResponse #[returns(IsFrozenResponse)] diff --git a/contracts/external/cw-vesting/Cargo.toml b/contracts/external/cw-vesting/Cargo.toml index 2689de7de..2f5204551 100644 --- a/contracts/external/cw-vesting/Cargo.toml +++ b/contracts/external/cw-vesting/Cargo.toml @@ -24,6 +24,7 @@ staking = ["cosmwasm-std/staking"] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-denom = { workspace = true } +cw-orch = { workspace = true } cw-ownable = { workspace = true } cw-stake-tracker = { workspace = true } cw-storage-plus = { workspace = true } diff --git a/contracts/external/cw-vesting/src/msg.rs b/contracts/external/cw-vesting/src/msg.rs index 5aac6ca17..a9a89e0ae 100644 --- a/contracts/external/cw-vesting/src/msg.rs +++ b/contracts/external/cw-vesting/src/msg.rs @@ -62,6 +62,7 @@ pub struct InstantiateMsg { #[cw_ownable_execute] #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { /// Fund the contract with a cw20 token. The `msg` field must have /// the shape `{"fund":{}}`, and the amount sent must be the same @@ -194,7 +195,7 @@ pub enum ReceiveMsg { } #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryMsg { /// Get the current ownership. #[returns(::cw_ownable::Ownership<::cosmwasm_std::Addr>)] diff --git a/contracts/external/cw721-roles/Cargo.toml b/contracts/external/cw721-roles/Cargo.toml index 1a6935974..c5a4ee1d6 100644 --- a/contracts/external/cw721-roles/Cargo.toml +++ b/contracts/external/cw721-roles/Cargo.toml @@ -25,6 +25,7 @@ cw-storage-plus = { workspace = true } cw-utils = { workspace = true } cw2 = { workspace = true } cw4 = { workspace = true } +cw-orch = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true, features = ["library"] } dao-cw721-extensions = { workspace = true } diff --git a/contracts/external/dao-migrator/Cargo.toml b/contracts/external/dao-migrator/Cargo.toml index 4d2b48cb8..877838d3c 100644 --- a/contracts/external/dao-migrator/Cargo.toml +++ b/contracts/external/dao-migrator/Cargo.toml @@ -24,6 +24,7 @@ cw-utils = { workspace = true } thiserror = { workspace = true } cw2 = { workspace = true } cw20 = { workspace = true } +cw-orch = { workspace = true } dao-interface = { workspace = true } dao-dao-core = { workspace = true, features = ["library"] } diff --git a/contracts/test/dao-proposal-sudo/src/msg.rs b/contracts/test/dao-proposal-sudo/src/msg.rs index de00f94ad..f6b6e32dc 100644 --- a/contracts/test/dao-proposal-sudo/src/msg.rs +++ b/contracts/test/dao-proposal-sudo/src/msg.rs @@ -9,7 +9,7 @@ pub struct InstantiateMsg { #[cw_serde] #[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { - #[fn_name("proposal_execute")] + #[cw_orch(fn_name("proposal_execute"))] Execute { msgs: Vec }, } diff --git a/packages/cw-orch/Cargo.toml b/packages/cw-orch/Cargo.toml index ebff3e21e..85123d248 100644 --- a/packages/cw-orch/Cargo.toml +++ b/packages/cw-orch/Cargo.toml @@ -18,6 +18,13 @@ 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 +cw721-roles = { path = "../../contracts/external/cw721-roles", version = "2.4.2" } +cw-admin-factory = { path = "../../contracts/external/cw-admin-factory", version = "2.5.0" } +cw-vesting = { version = "2.4.2", path = "../../contracts/external/cw-vesting" } +cw-payroll-factory = { version = "2.4.2", path = "../../contracts/external/cw-payroll-factory" } +cw-token-swap = { version = "2.4.2", path = "../../contracts/external/cw-token-swap" } +cw-tokenfactory-issuer = { version = "2.4.2", path = "../../contracts/external/cw-tokenfactory-issuer" } +dao-migrator = { version = "2.4.2", path = "../../contracts/external/dao-migrator" } 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" } diff --git a/packages/cw-orch/src/core.rs b/packages/cw-orch/src/core.rs index ff7fd24ea..501d56f4e 100644 --- a/packages/cw-orch/src/core.rs +++ b/packages/cw-orch/src/core.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +use cw_orch::{interface, prelude::*}; use dao_dao_core::contract::{execute, instantiate, migrate, query, reply}; use dao_interface::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; @@ -8,7 +6,6 @@ use dao_interface::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] pub struct DaoDaoCore; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for DaoDaoCore { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/cw-orch/src/external/admin_factory.rs b/packages/cw-orch/src/external/admin_factory.rs new file mode 100644 index 000000000..065b26ec4 --- /dev/null +++ b/packages/cw-orch/src/external/admin_factory.rs @@ -0,0 +1,20 @@ +use cw_orch::{interface, prelude::*}; + +use cw_admin_factory::contract::{execute, instantiate, query, reply}; +use cw_admin_factory::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] +pub struct DaoExternalAdminFactory; + +impl Uploadable for DaoExternalAdminFactory { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_admin_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/external/cw721_roles.rs b/packages/cw-orch/src/external/cw721_roles.rs new file mode 100644 index 000000000..f7fd355d0 --- /dev/null +++ b/packages/cw-orch/src/external/cw721_roles.rs @@ -0,0 +1,20 @@ +use cw_orch::{interface, prelude::*}; + +use cw721_roles::contract::{execute, instantiate, query}; +use cw721_roles::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] +pub struct DaoExternalCw721Roles; + +impl Uploadable for DaoExternalCw721Roles { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_cw721_roles") + .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/external/cw_vesting.rs b/packages/cw-orch/src/external/cw_vesting.rs new file mode 100644 index 000000000..a533b0085 --- /dev/null +++ b/packages/cw-orch/src/external/cw_vesting.rs @@ -0,0 +1,20 @@ +use cw_orch::{interface, prelude::*}; + +use cw_vesting::contract::{execute, instantiate, query}; +use cw_vesting::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] +pub struct DaoExternalCwVesting; + +impl Uploadable for DaoExternalCwVesting { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_cw_vesting") + .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/external/migrator.rs b/packages/cw-orch/src/external/migrator.rs new file mode 100644 index 000000000..6f3ae1dad --- /dev/null +++ b/packages/cw-orch/src/external/migrator.rs @@ -0,0 +1,20 @@ +use cw_orch::{interface, prelude::*}; + +use dao_migrator::contract::{execute, instantiate, query, reply}; +use dao_migrator::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] +pub struct DaoExternalMigrator; + +impl Uploadable for DaoExternalMigrator { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_migrator") + .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/external/mod.rs b/packages/cw-orch/src/external/mod.rs index 2409f5475..092ef1db2 100644 --- a/packages/cw-orch/src/external/mod.rs +++ b/packages/cw-orch/src/external/mod.rs @@ -1,3 +1,17 @@ +mod admin_factory; +mod cw721_roles; mod cw_abc; +mod cw_vesting; +mod migrator; +mod payroll_factory; +mod token_swap; +mod tokenfactory_issuer; +pub use admin_factory::DaoExternalAdminFactory; +pub use cw721_roles::DaoExternalCw721Roles; pub use cw_abc::CwAbc; +pub use cw_vesting::DaoExternalCwVesting; +pub use migrator::DaoExternalMigrator; +pub use payroll_factory::DaoExternalPayrollFactory; +pub use token_swap::DaoExternalTokenSwap; +pub use tokenfactory_issuer::DaoExternalTokenfactoryIssuer; diff --git a/packages/cw-orch/src/external/payroll_factory.rs b/packages/cw-orch/src/external/payroll_factory.rs new file mode 100644 index 000000000..28af998bd --- /dev/null +++ b/packages/cw-orch/src/external/payroll_factory.rs @@ -0,0 +1,20 @@ +use cw_orch::{interface, prelude::*}; + +use cw_payroll_factory::contract::{execute, instantiate, query, reply}; +use cw_payroll_factory::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] +pub struct DaoExternalPayrollFactory; + +impl Uploadable for DaoExternalPayrollFactory { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_payroll_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/external/token_swap.rs b/packages/cw-orch/src/external/token_swap.rs new file mode 100644 index 000000000..39ddcea10 --- /dev/null +++ b/packages/cw-orch/src/external/token_swap.rs @@ -0,0 +1,20 @@ +use cw_orch::{interface, prelude::*}; + +use cw_token_swap::contract::{execute, instantiate, migrate, query}; +use cw_token_swap::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] +pub struct DaoExternalTokenSwap; + +impl Uploadable for DaoExternalTokenSwap { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_tokenswap") + .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/external/tokenfactory_issuer.rs b/packages/cw-orch/src/external/tokenfactory_issuer.rs new file mode 100644 index 000000000..95e7f834c --- /dev/null +++ b/packages/cw-orch/src/external/tokenfactory_issuer.rs @@ -0,0 +1,24 @@ +use cw_orch::{interface, prelude::*}; + +use cw_tokenfactory_issuer::contract::{execute, instantiate, migrate, query, reply}; +use cw_tokenfactory_issuer::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] +pub struct DaoExternalTokenfactoryIssuer; + +impl Uploadable for DaoExternalTokenfactoryIssuer { + /// Return the path to the wasm file corresponding to the contract + fn wasm(_chain: &ChainInfoOwned) -> WasmPath { + artifacts_dir_from_workspace!() + .find_wasm_path("dao_tokenfactory_issuer") + .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/lib.rs b/packages/cw-orch/src/lib.rs index 7a3794426..23b7031a5 100644 --- a/packages/cw-orch/src/lib.rs +++ b/packages/cw-orch/src/lib.rs @@ -1,17 +1,31 @@ +#[cfg(not(target_arch = "wasm32"))] mod core; +#[cfg(not(target_arch = "wasm32"))] mod external; +#[cfg(not(target_arch = "wasm32"))] mod pre_propose; +#[cfg(not(target_arch = "wasm32"))] mod proposal; +#[cfg(not(target_arch = "wasm32"))] mod staking; +#[cfg(not(target_arch = "wasm32"))] mod test_contracts; +#[cfg(not(target_arch = "wasm32"))] mod voting; +#[cfg(not(target_arch = "wasm32"))] pub use core::*; +#[cfg(not(target_arch = "wasm32"))] pub use external::*; +#[cfg(not(target_arch = "wasm32"))] pub use pre_propose::*; +#[cfg(not(target_arch = "wasm32"))] pub use proposal::*; +#[cfg(not(target_arch = "wasm32"))] pub use staking::*; +#[cfg(not(target_arch = "wasm32"))] pub use test_contracts::*; +#[cfg(not(target_arch = "wasm32"))] pub use voting::*; #[cfg(feature = "wasm_test")] diff --git a/packages/cw-orch/src/pre_propose/approval_single.rs b/packages/cw-orch/src/pre_propose/approval_single.rs index 6dffb7e26..98b9927f6 100644 --- a/packages/cw-orch/src/pre_propose/approval_single.rs +++ b/packages/cw-orch/src/pre_propose/approval_single.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +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}; @@ -8,7 +6,6 @@ use dao_pre_propose_approval_single::msg::{ExecuteMsg, InstantiateMsg, QueryMsg} #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] pub struct DaoPreProposeApprovalSingle; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for DaoPreProposeApprovalSingle { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/cw-orch/src/pre_propose/approver.rs b/packages/cw-orch/src/pre_propose/approver.rs index 85db3924d..cebd7c13a 100644 --- a/packages/cw-orch/src/pre_propose/approver.rs +++ b/packages/cw-orch/src/pre_propose/approver.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +use cw_orch::{interface, prelude::*}; use dao_pre_propose_approver::contract::{execute, instantiate, query}; use dao_pre_propose_approver::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -8,7 +6,6 @@ use dao_pre_propose_approver::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] pub struct DaoPreProposeApprover; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for DaoPreProposeApprover { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/cw-orch/src/pre_propose/multiple.rs b/packages/cw-orch/src/pre_propose/multiple.rs index 4c5cafdb4..7517df320 100644 --- a/packages/cw-orch/src/pre_propose/multiple.rs +++ b/packages/cw-orch/src/pre_propose/multiple.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +use cw_orch::{interface, prelude::*}; use dao_pre_propose_multiple::contract::{execute, instantiate, query}; use dao_pre_propose_multiple::contract::{ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -8,7 +6,6 @@ use dao_pre_propose_multiple::contract::{ExecuteMsg, InstantiateMsg, QueryMsg}; #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] pub struct DaoPreProposeMultiple; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for DaoPreProposeMultiple { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/cw-orch/src/pre_propose/single.rs b/packages/cw-orch/src/pre_propose/single.rs index 5934c3e45..891654455 100644 --- a/packages/cw-orch/src/pre_propose/single.rs +++ b/packages/cw-orch/src/pre_propose/single.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +use cw_orch::{interface, prelude::*}; use dao_pre_propose_single::contract::{execute, instantiate, query}; use dao_pre_propose_single::contract::{ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -8,7 +6,6 @@ use dao_pre_propose_single::contract::{ExecuteMsg, InstantiateMsg, QueryMsg}; #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] pub struct DaoPreProposeSingle; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for DaoPreProposeSingle { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/cw-orch/src/proposal/condorcet.rs b/packages/cw-orch/src/proposal/condorcet.rs index 5bfce3f22..fdfb8fb50 100644 --- a/packages/cw-orch/src/proposal/condorcet.rs +++ b/packages/cw-orch/src/proposal/condorcet.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +use cw_orch::{interface, prelude::*}; use dao_proposal_condorcet::contract::{execute, instantiate, query, reply}; use dao_proposal_condorcet::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -8,7 +6,6 @@ use dao_proposal_condorcet::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] pub struct DaoProposalCondorcet; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for DaoProposalCondorcet { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/cw-orch/src/proposal/multiple.rs b/packages/cw-orch/src/proposal/multiple.rs index d5d482ae6..dc28e8ef0 100644 --- a/packages/cw-orch/src/proposal/multiple.rs +++ b/packages/cw-orch/src/proposal/multiple.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +use cw_orch::{interface, prelude::*}; use dao_proposal_multiple::contract::{execute, instantiate, migrate, query, reply}; use dao_proposal_multiple::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; @@ -8,7 +6,6 @@ use dao_proposal_multiple::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMs #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] pub struct DaoProposalMultiple; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for DaoProposalMultiple { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/cw-orch/src/proposal/single.rs b/packages/cw-orch/src/proposal/single.rs index c84617f21..7db09ee3b 100644 --- a/packages/cw-orch/src/proposal/single.rs +++ b/packages/cw-orch/src/proposal/single.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +use cw_orch::{interface, prelude::*}; use dao_proposal_single::contract::{execute, instantiate, migrate, query, reply}; use dao_proposal_single::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; @@ -8,7 +6,6 @@ use dao_proposal_single::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg} #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] pub struct DaoProposalSingle; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for DaoProposalSingle { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/cw-orch/src/staking/cw20_stake.rs b/packages/cw-orch/src/staking/cw20_stake.rs index bfbfeb054..8b43d844c 100644 --- a/packages/cw-orch/src/staking/cw20_stake.rs +++ b/packages/cw-orch/src/staking/cw20_stake.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +use cw_orch::{interface, prelude::*}; use cw20_stake::contract::{execute, instantiate, migrate, query}; use cw20_stake::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; @@ -8,7 +6,6 @@ use cw20_stake::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] pub struct Cw20Stake; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for Cw20Stake { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/cw-orch/src/staking/external_rewards.rs b/packages/cw-orch/src/staking/external_rewards.rs index 919b66b3d..0d4f19c5c 100644 --- a/packages/cw-orch/src/staking/external_rewards.rs +++ b/packages/cw-orch/src/staking/external_rewards.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +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}; @@ -8,7 +6,6 @@ use cw20_stake_external_rewards::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, Q #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] pub struct Cw20StakeExternalRewards; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for Cw20StakeExternalRewards { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/cw-orch/src/staking/reward_distributor.rs b/packages/cw-orch/src/staking/reward_distributor.rs index cc6e4f7a8..2f398ca81 100644 --- a/packages/cw-orch/src/staking/reward_distributor.rs +++ b/packages/cw-orch/src/staking/reward_distributor.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +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}; @@ -8,7 +6,6 @@ use cw20_stake_reward_distributor::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] pub struct Cw20StakeRewardDistributor; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for Cw20StakeRewardDistributor { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/cw-orch/src/test_contracts/cw721.rs b/packages/cw-orch/src/test_contracts/cw721.rs index 2959d2090..8f1097f08 100644 --- a/packages/cw-orch/src/test_contracts/cw721.rs +++ b/packages/cw-orch/src/test_contracts/cw721.rs @@ -3,15 +3,12 @@ use cw721_base::{ entry::{execute, instantiate, query}, ExecuteMsg, InstantiateMsg, QueryMsg, }; -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +use cw_orch::{interface, prelude::*}; pub type Cw721BaseQueryMsg = QueryMsg; #[interface(InstantiateMsg, ExecuteMsg, Cw721BaseQueryMsg, Empty)] pub struct Cw721BaseGeneric; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for Cw721BaseGeneric { // Return a CosmWasm contract wrapper fn wrapper() -> Box> { @@ -19,5 +16,4 @@ impl Uploadable for Cw721BaseGeneric { } } -#[cfg(not(target_arch = "wasm32"))] pub type Cw721Base = Cw721BaseGeneric, Empty>; diff --git a/packages/cw-orch/src/test_contracts/proposal_hook_counter.rs b/packages/cw-orch/src/test_contracts/proposal_hook_counter.rs index 5702cf132..7dc26654e 100644 --- a/packages/cw-orch/src/test_contracts/proposal_hook_counter.rs +++ b/packages/cw-orch/src/test_contracts/proposal_hook_counter.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +use cw_orch::{interface, prelude::*}; use dao_proposal_hook_counter::contract::{execute, instantiate, query}; use dao_proposal_hook_counter::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -8,7 +6,6 @@ use dao_proposal_hook_counter::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] pub struct DaoProposalHookCounter; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for DaoProposalHookCounter { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/cw-orch/src/test_contracts/proposal_sudo.rs b/packages/cw-orch/src/test_contracts/proposal_sudo.rs index 27ba05201..bd623624a 100644 --- a/packages/cw-orch/src/test_contracts/proposal_sudo.rs +++ b/packages/cw-orch/src/test_contracts/proposal_sudo.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +use cw_orch::{interface, prelude::*}; use dao_proposal_sudo::contract::{execute, instantiate, query}; use dao_proposal_sudo::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -8,7 +6,6 @@ use dao_proposal_sudo::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] pub struct DaoProposalSudo; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for DaoProposalSudo { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/cw-orch/src/test_contracts/test_custom_factory.rs b/packages/cw-orch/src/test_contracts/test_custom_factory.rs index e6dd8e963..3424df887 100644 --- a/packages/cw-orch/src/test_contracts/test_custom_factory.rs +++ b/packages/cw-orch/src/test_contracts/test_custom_factory.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +use cw_orch::{interface, prelude::*}; use dao_test_custom_factory::contract::{execute, instantiate, query, reply}; use dao_test_custom_factory::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -8,7 +6,6 @@ use dao_test_custom_factory::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] pub struct DaoTestCustomFactory; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for DaoTestCustomFactory { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/cw-orch/src/test_contracts/voting_cw20_balance.rs b/packages/cw-orch/src/test_contracts/voting_cw20_balance.rs index 15b8c2cb3..fdf7b84c0 100644 --- a/packages/cw-orch/src/test_contracts/voting_cw20_balance.rs +++ b/packages/cw-orch/src/test_contracts/voting_cw20_balance.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +use cw_orch::{interface, prelude::*}; use dao_voting_cw20_balance::contract::{execute, instantiate, query, reply}; use dao_voting_cw20_balance::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -8,7 +6,6 @@ use dao_voting_cw20_balance::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] pub struct DaoVotingCw20Balance; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for DaoVotingCw20Balance { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/cw-orch/src/voting/cw20_staked.rs b/packages/cw-orch/src/voting/cw20_staked.rs index 33ee57ed3..1f044a913 100644 --- a/packages/cw-orch/src/voting/cw20_staked.rs +++ b/packages/cw-orch/src/voting/cw20_staked.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +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}; @@ -8,7 +6,6 @@ use dao_voting_cw20_staked::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryM #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] pub struct DaoVotingCw20Staked; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for DaoVotingCw20Staked { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/cw-orch/src/voting/cw4.rs b/packages/cw-orch/src/voting/cw4.rs index d230cd5a1..11dfac5ea 100644 --- a/packages/cw-orch/src/voting/cw4.rs +++ b/packages/cw-orch/src/voting/cw4.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +use cw_orch::{interface, prelude::*}; use dao_voting_cw4::contract::{execute, instantiate, migrate, query, reply}; use dao_voting_cw4::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; @@ -8,7 +6,6 @@ use dao_voting_cw4::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] pub struct DaoVotingCw4; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for DaoVotingCw4 { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/cw-orch/src/voting/cw721_roles.rs b/packages/cw-orch/src/voting/cw721_roles.rs index 395d3b0ed..6469a0887 100644 --- a/packages/cw-orch/src/voting/cw721_roles.rs +++ b/packages/cw-orch/src/voting/cw721_roles.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +use cw_orch::{interface, prelude::*}; use dao_voting_cw721_roles::contract::{execute, instantiate, query, reply}; use dao_voting_cw721_roles::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -8,7 +6,6 @@ use dao_voting_cw721_roles::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] pub struct DaoVotingCw721Roles; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for DaoVotingCw721Roles { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/cw-orch/src/voting/cw721_staked.rs b/packages/cw-orch/src/voting/cw721_staked.rs index e5b4bb02d..5ed2f1ce1 100644 --- a/packages/cw-orch/src/voting/cw721_staked.rs +++ b/packages/cw-orch/src/voting/cw721_staked.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +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}; @@ -8,7 +6,6 @@ use dao_voting_cw721_staked::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, Query #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] pub struct DaoVotingCw721Staked; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for DaoVotingCw721Staked { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/cw-orch/src/voting/token_staked.rs b/packages/cw-orch/src/voting/token_staked.rs index 733e00260..a8f41ca94 100644 --- a/packages/cw-orch/src/voting/token_staked.rs +++ b/packages/cw-orch/src/voting/token_staked.rs @@ -1,6 +1,4 @@ -use cw_orch::interface; -#[cfg(not(target_arch = "wasm32"))] -use cw_orch::prelude::*; +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}; @@ -8,7 +6,6 @@ use dao_voting_token_staked::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, Query #[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)] pub struct DaoVotingTokenStaked; -#[cfg(not(target_arch = "wasm32"))] impl Uploadable for DaoVotingTokenStaked { /// Return the path to the wasm file corresponding to the contract fn wasm(_chain: &ChainInfoOwned) -> WasmPath { diff --git a/packages/dao-cw721-extensions/Cargo.toml b/packages/dao-cw721-extensions/Cargo.toml index 609e89d0d..f3d2ca49e 100644 --- a/packages/dao-cw721-extensions/Cargo.toml +++ b/packages/dao-cw721-extensions/Cargo.toml @@ -12,3 +12,4 @@ cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } cw-controllers = { workspace = true } cw4 = { workspace = true } +cw-orch = { workspace = true } diff --git a/packages/dao-cw721-extensions/src/roles.rs b/packages/dao-cw721-extensions/src/roles.rs index 0f2c9166a..a303e508e 100644 --- a/packages/dao-cw721-extensions/src/roles.rs +++ b/packages/dao-cw721-extensions/src/roles.rs @@ -10,6 +10,7 @@ pub struct MetadataExt { } #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteExt { /// Add a new hook to be informed of all membership changes. /// Must be called by Admin @@ -32,7 +33,7 @@ pub enum ExecuteExt { impl CustomMsg for ExecuteExt {} #[cw_serde] -#[derive(QueryResponses)] +#[derive(QueryResponses, cw_orch::QueryFns)] pub enum QueryExt { /// Total weight at a given height #[returns(cw4::TotalWeightResponse)]