diff --git a/Cargo.lock b/Cargo.lock index 881d2be4..e1095b72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -487,26 +487,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cw4-voting" -version = "0.1.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cosmwasm-storage", - "cw-multi-test", - "cw-storage-plus 0.13.4", - "cw-utils", - "cw2", - "cw4", - "cw4-group", - "cwd-interface", - "cwd-macros", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "cw721" version = "0.13.4" diff --git a/Cargo.toml b/Cargo.toml index b3274352..ef7d371a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ members = [ "contracts/subdaos/cwd-subdao-core", "contracts/subdaos/proposal/*", "contracts/subdaos/pre-propose/*", - "contracts/subdaos/voting/*", "contracts/subdaos/cwd-subdao-timelock-single", "contracts/tokenomics/treasury", diff --git a/artifacts/checksums.txt b/artifacts/checksums.txt index 0461788d..911cf434 100644 --- a/artifacts/checksums.txt +++ b/artifacts/checksums.txt @@ -6,7 +6,7 @@ c33b583b61ec1890fcd00cc7b5ece08de2c30d93e39038680e480f4fa3de58ce cwd_pre_propos dc3dea39ec18ff703c3f7211da1c191816bb6c36395653418ae575c6c6f9e195 cwd_subdao_core.wasm 6e3ebc6a45823e7261b057e3df5bfc263285351254b4a58e8d67b241121593e9 cwd_subdao_pre_propose_single.wasm 3117255502cc3c1e523fafa89ce1b949b1d3a3e6b15ffb0f6b26a9225c0a867c cwd_subdao_proposal_single.wasm -70ef5c53353626b651a8bf1c962d04353dd6af3675210d29d7a4e47f07412543 cwd_subdao_timelock_single.wasm +d0d99da5462a0af5f7b5274551a15b4c0dab2db5b44b4e3f8f54dafb383ab76f cwd_subdao_timelock_single.wasm 5f4303155a3c9e88b80f0e8805ffbd73fccd9afd82e5de442f33f8aa659bb3d3 neutron_distribution.wasm 0fb4675c3ef58d7745fbc05941c0aecc7f44aad1aa3f21edafa147cef917cc44 neutron_reserve.wasm 7bc1f2351e096157d89887c240f2bef5493ce1b208e23c9b31ce7848921acaf8 neutron_timelock_single.wasm diff --git a/artifacts/checksums_intermediate.txt b/artifacts/checksums_intermediate.txt index 7c468ae6..c88d0a92 100644 --- a/artifacts/checksums_intermediate.txt +++ b/artifacts/checksums_intermediate.txt @@ -11,4 +11,4 @@ d125e4bffb0973e8ea8ba4f0cd765aa77d190ddd08b8395d900c717462580c0d target/wasm32- 48d688a9cf6416802d21a2e34ef0a8dbd2200f86f173385dfac7b20b2298ef40 target/wasm32-unknown-unknown/release/cwd_subdao_core.wasm 05eef2951a1721533c1be74f4ec208885f0106b0d9513fc1aa85f51b7c1bfd7d target/wasm32-unknown-unknown/release/cwd_subdao_proposal_single.wasm e766ea0c069acc605b924d5e8ecbc0376026347c275501ad67c391d978ddeaef target/wasm32-unknown-unknown/release/cwd_subdao_pre_propose_single.wasm -f1a3426486c366e79717d07c164a9eac6d8024b6f7c943c741d520bfa997698b target/wasm32-unknown-unknown/release/cwd_subdao_timelock_single.wasm +044ffbc1a155455ca6c31362f905b5b43a89baa5146f074a0866b5fab6e8d4e5 target/wasm32-unknown-unknown/release/cwd_subdao_timelock_single.wasm diff --git a/artifacts/cwd_subdao_timelock_single.wasm b/artifacts/cwd_subdao_timelock_single.wasm index 7c770713..a3901445 100644 Binary files a/artifacts/cwd_subdao_timelock_single.wasm and b/artifacts/cwd_subdao_timelock_single.wasm differ diff --git a/contracts/subdaos/cwd-subdao-timelock-single/src/contract.rs b/contracts/subdaos/cwd-subdao-timelock-single/src/contract.rs index e223026a..41e8a2d0 100644 --- a/contracts/subdaos/cwd-subdao-timelock-single/src/contract.rs +++ b/contracts/subdaos/cwd-subdao-timelock-single/src/contract.rs @@ -184,7 +184,7 @@ pub fn execute_update_config( new_timelock_duration: Option, ) -> Result, ContractError> { let mut config: Config = CONFIG.load(deps.storage)?; - if Some(info.sender.clone()) != config.owner { + if Some(info.sender) != config.owner { return Err(ContractError::Unauthorized {}); } @@ -192,11 +192,9 @@ pub fn execute_update_config( .map(|new_owner| deps.api.addr_validate(&new_owner)) .transpose()?; - if Some(info.sender) != config.owner && new_owner != config.owner { - return Err(ContractError::OnlyOwnerCanChangeOwner {}); - }; - - config.owner = new_owner; + if new_owner.is_some() { + config.owner = new_owner; + } if let Some(timelock_duration) = new_timelock_duration { config.timelock_duration = timelock_duration; diff --git a/contracts/subdaos/cwd-subdao-timelock-single/src/lib.rs b/contracts/subdaos/cwd-subdao-timelock-single/src/lib.rs index acba49da..142a5f95 100644 --- a/contracts/subdaos/cwd-subdao-timelock-single/src/lib.rs +++ b/contracts/subdaos/cwd-subdao-timelock-single/src/lib.rs @@ -7,6 +7,6 @@ pub mod proposal; pub mod state; #[cfg(test)] -mod tests; +mod testing; pub use crate::error::ContractError; diff --git a/contracts/subdaos/cwd-subdao-timelock-single/src/proposal.rs b/contracts/subdaos/cwd-subdao-timelock-single/src/proposal.rs index cd4071ef..c2c9d78b 100644 --- a/contracts/subdaos/cwd-subdao-timelock-single/src/proposal.rs +++ b/contracts/subdaos/cwd-subdao-timelock-single/src/proposal.rs @@ -3,7 +3,7 @@ use neutron_bindings::bindings::msg::NeutronMsg; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, JsonSchema, Debug)] +#[derive(Serialize, Deserialize, Clone, JsonSchema, Debug, Eq, PartialEq)] pub struct SingleChoiceProposal { /// The ID of the proposal being returned. pub id: u64, diff --git a/contracts/subdaos/cwd-subdao-timelock-single/src/testing/mock_querier.rs b/contracts/subdaos/cwd-subdao-timelock-single/src/testing/mock_querier.rs new file mode 100644 index 00000000..a6d947f2 --- /dev/null +++ b/contracts/subdaos/cwd-subdao-timelock-single/src/testing/mock_querier.rs @@ -0,0 +1,71 @@ +use std::marker::PhantomData; + +use cosmwasm_std::{ + from_binary, from_slice, + testing::{MockApi, MockQuerier, MockStorage}, + to_binary, ContractResult, Empty, OwnedDeps, Querier, QuerierResult, QueryRequest, SystemError, + SystemResult, WasmQuery, +}; +use cwd_pre_propose_base::msg::QueryMsg as PreProposeQuery; + +pub const MOCK_SUBDAO_CORE_ADDR: &str = "neutron1subdao_core_contract"; +pub const MOCK_TIMELOCK_INITIALIZER: &str = "neutron1timelock_initializer"; + +pub fn mock_dependencies() -> OwnedDeps { + let custom_querier = WasmMockQuerier::new(MockQuerier::new(&[])); + + OwnedDeps { + storage: MockStorage::default(), + api: MockApi::default(), + querier: custom_querier, + custom_query_type: PhantomData, + } +} + +pub struct WasmMockQuerier { + base: MockQuerier, +} + +impl Querier for WasmMockQuerier { + fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { + let request: QueryRequest = match from_slice(bin_request) { + Ok(v) => v, + Err(e) => { + return QuerierResult::Err(SystemError::InvalidRequest { + error: format!("Parsing query request: {}", e), + request: bin_request.into(), + }); + } + }; + self.handle_query(&request) + } +} + +impl WasmMockQuerier { + pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { + match &request { + QueryRequest::Wasm(WasmQuery::Smart { contract_addr, msg }) => { + if contract_addr == MOCK_TIMELOCK_INITIALIZER { + let q: PreProposeQuery = from_binary(msg).unwrap(); + let addr = match q { + PreProposeQuery::ProposalModule {} => todo!(), + PreProposeQuery::Dao {} => MOCK_SUBDAO_CORE_ADDR, + PreProposeQuery::Config {} => todo!(), + PreProposeQuery::DepositInfo { proposal_id: _ } => todo!(), + }; + return SystemResult::Ok(ContractResult::from(to_binary(addr))); + } + SystemResult::Err(SystemError::NoSuchContract { + addr: contract_addr.to_string(), + }) + } + _ => self.base.handle_query(request), + } + } +} + +impl WasmMockQuerier { + pub fn new(base: MockQuerier) -> Self { + WasmMockQuerier { base } + } +} diff --git a/contracts/subdaos/cwd-subdao-timelock-single/src/testing/mod.rs b/contracts/subdaos/cwd-subdao-timelock-single/src/testing/mod.rs new file mode 100644 index 00000000..a1e507b6 --- /dev/null +++ b/contracts/subdaos/cwd-subdao-timelock-single/src/testing/mod.rs @@ -0,0 +1,2 @@ +mod mock_querier; +mod tests; diff --git a/contracts/subdaos/cwd-subdao-timelock-single/src/testing/tests.rs b/contracts/subdaos/cwd-subdao-timelock-single/src/testing/tests.rs new file mode 100644 index 00000000..546aa787 --- /dev/null +++ b/contracts/subdaos/cwd-subdao-timelock-single/src/testing/tests.rs @@ -0,0 +1,502 @@ +use cosmwasm_std::{ + from_binary, + testing::{mock_env, mock_info}, + Addr, Attribute, Reply, SubMsg, SubMsgResult, +}; +use neutron_bindings::bindings::msg::NeutronMsg; +use neutron_timelock::single::ExecuteMsg; + +use crate::{ + contract::{execute, instantiate, query, reply}, + msg::{InstantiateMsg, ProposalListResponse, QueryMsg}, + proposal::{ProposalStatus, SingleChoiceProposal}, + state::{Config, CONFIG, DEFAULT_LIMIT, PROPOSALS}, + testing::mock_querier::MOCK_TIMELOCK_INITIALIZER, +}; + +use super::mock_querier::{mock_dependencies, MOCK_SUBDAO_CORE_ADDR}; + +#[test] +fn test_instantiate_test() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info("neutron1unknownsender", &[]); + let msg = InstantiateMsg { + owner: Some(Addr::unchecked("dao")), + timelock_duration: 10, + }; + let res = instantiate(deps.as_mut(), env.clone(), info, msg); + assert_eq!( + "Generic error: Querier system error: No such contract: neutron1unknownsender", + res.unwrap_err().to_string() + ); + + let info = mock_info(MOCK_TIMELOCK_INITIALIZER, &[]); + + let msg = InstantiateMsg { + owner: Some(Addr::unchecked("dao")), + timelock_duration: 10, + }; + let res = instantiate(deps.as_mut(), env.clone(), info.clone(), msg.clone()); + let res_ok = res.unwrap(); + let expected_attributes = vec![ + Attribute::new("action", "instantiate"), + Attribute::new("owner", "dao"), + Attribute::new("timelock_duration", "10"), + ]; + assert_eq!(expected_attributes, res_ok.attributes); + let config = CONFIG.load(&deps.storage).unwrap(); + let expected_config = Config { + owner: msg.owner, + timelock_duration: msg.timelock_duration, + subdao: Addr::unchecked(MOCK_SUBDAO_CORE_ADDR), + }; + assert_eq!(expected_config, config); + + let msg = InstantiateMsg { + owner: None, + timelock_duration: 10, + }; + let res = instantiate(deps.as_mut(), env, info, msg.clone()); + let res_ok = res.unwrap(); + let expected_attributes = vec![ + Attribute::new("action", "instantiate"), + Attribute::new("owner", "None"), + Attribute::new("timelock_duration", "10"), + ]; + assert_eq!(expected_attributes, res_ok.attributes); + let config = CONFIG.load(&deps.storage).unwrap(); + let expected_config = Config { + owner: msg.owner, + timelock_duration: msg.timelock_duration, + subdao: Addr::unchecked(MOCK_SUBDAO_CORE_ADDR), + }; + assert_eq!(expected_config, config); +} + +#[test] +fn test_execute_timelock_proposal() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info("neutron1unknownsender", &[]); + + let msg = ExecuteMsg::TimelockProposal { + proposal_id: 10, + msgs: vec![NeutronMsg::remove_interchain_query(1).into()], + }; + + let res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); + assert_eq!( + "cwd_subdao_timelock_single::state::Config not found", + res.unwrap_err().to_string() + ); + + let config = Config { + owner: Some(Addr::unchecked("owner")), + timelock_duration: 10, + subdao: Addr::unchecked(MOCK_SUBDAO_CORE_ADDR), + }; + CONFIG.save(deps.as_mut().storage, &config).unwrap(); + let res = execute(deps.as_mut(), env.clone(), info, msg.clone()); + assert_eq!("Unauthorized", res.unwrap_err().to_string()); + + let info = mock_info(MOCK_SUBDAO_CORE_ADDR, &[]); + let res_ok = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); + let expected_attributes = vec![ + Attribute::new("action", "timelock_proposal"), + Attribute::new("sender", MOCK_SUBDAO_CORE_ADDR), + Attribute::new("proposal_id", "10"), + Attribute::new("status", "timelocked"), + ]; + assert_eq!(expected_attributes, res_ok.attributes); + assert_eq!(0, res_ok.messages.len()); + + let expected_proposal = SingleChoiceProposal { + id: 10, + timelock_ts: env.block.time, + msgs: vec![NeutronMsg::remove_interchain_query(1).into()], + status: ProposalStatus::Timelocked, + }; + let prop = PROPOSALS.load(deps.as_mut().storage, 10u64).unwrap(); + assert_eq!(expected_proposal, prop); +} + +#[test] +fn test_execute_proposal() { + let mut deps = mock_dependencies(); + let mut env = mock_env(); + let info = mock_info("neutron1unknownsender", &[]); + + let msg = ExecuteMsg::ExecuteProposal { proposal_id: 10 }; + + let res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); + assert_eq!( + "cwd_subdao_timelock_single::state::Config not found", + res.unwrap_err().to_string() + ); + + let config = Config { + owner: Some(Addr::unchecked("owner")), + timelock_duration: 10, + subdao: Addr::unchecked(MOCK_SUBDAO_CORE_ADDR), + }; + CONFIG.save(deps.as_mut().storage, &config).unwrap(); + let res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); + assert_eq!( + "cwd_subdao_timelock_single::proposal::SingleChoiceProposal not found", + res.unwrap_err().to_string() + ); + + let wrong_prop_statuses = vec![ + ProposalStatus::Executed, + ProposalStatus::ExecutionFailed, + ProposalStatus::Overruled, + ]; + for s in wrong_prop_statuses { + let proposal = SingleChoiceProposal { + id: 10, + timelock_ts: env.block.time, + msgs: vec![NeutronMsg::remove_interchain_query(1).into()], + status: s, + }; + PROPOSALS + .save(deps.as_mut().storage, proposal.id, &proposal) + .unwrap(); + let res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); + assert_eq!( + format!("Wrong proposal status ({})", s), + res.unwrap_err().to_string() + ) + } + + let proposal = SingleChoiceProposal { + id: 10, + timelock_ts: env.block.time, + msgs: vec![NeutronMsg::remove_interchain_query(1).into()], + status: ProposalStatus::Timelocked, + }; + PROPOSALS + .save(deps.as_mut().storage, proposal.id, &proposal) + .unwrap(); + let res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); + assert_eq!("Proposal is timelocked", res.unwrap_err().to_string()); + + env.block.time = env.block.time.plus_seconds(11); + let res = execute(deps.as_mut(), env, info, msg).unwrap(); + let expected_attributes = vec![ + Attribute::new("action", "execute_proposal"), + Attribute::new("sender", "neutron1unknownsender"), + Attribute::new("proposal_id", "10"), + ]; + assert_eq!(expected_attributes, res.attributes); + assert_eq!( + proposal + .msgs + .iter() + .map(|msg| SubMsg::reply_on_error(msg.clone(), proposal.id)) + .collect::>>(), + res.messages + ); + let updated_prop = PROPOSALS.load(deps.as_mut().storage, 10).unwrap(); + assert_eq!(ProposalStatus::Executed, updated_prop.status) +} + +#[test] +fn test_overrule_proposal() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info("neutron1unknownsender", &[]); + + let msg = ExecuteMsg::OverruleProposal { proposal_id: 10 }; + + let res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); + assert_eq!( + "cwd_subdao_timelock_single::state::Config not found", + res.unwrap_err().to_string() + ); + + let config = Config { + owner: Some(Addr::unchecked("owner")), + timelock_duration: 10, + subdao: Addr::unchecked(MOCK_SUBDAO_CORE_ADDR), + }; + CONFIG.save(deps.as_mut().storage, &config).unwrap(); + let res = execute(deps.as_mut(), env.clone(), info, msg.clone()); + assert_eq!("Unauthorized", res.unwrap_err().to_string()); + + let info = mock_info("owner", &[]); + + let wrong_prop_statuses = vec![ + ProposalStatus::Executed, + ProposalStatus::ExecutionFailed, + ProposalStatus::Overruled, + ]; + for s in wrong_prop_statuses { + let proposal = SingleChoiceProposal { + id: 10, + timelock_ts: env.block.time, + msgs: vec![NeutronMsg::remove_interchain_query(1).into()], + status: s, + }; + PROPOSALS + .save(deps.as_mut().storage, proposal.id, &proposal) + .unwrap(); + let res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); + assert_eq!( + format!("Wrong proposal status ({})", s), + res.unwrap_err().to_string() + ) + } + + let proposal = SingleChoiceProposal { + id: 10, + timelock_ts: env.block.time, + msgs: vec![NeutronMsg::remove_interchain_query(1).into()], + status: ProposalStatus::Timelocked, + }; + PROPOSALS + .save(deps.as_mut().storage, proposal.id, &proposal) + .unwrap(); + let res_ok = execute(deps.as_mut(), env, info.clone(), msg).unwrap(); + assert_eq!(0, res_ok.messages.len()); + let expected_attributes = vec![ + Attribute::new("action", "overrule_proposal"), + Attribute::new("sender", info.sender), + Attribute::new("proposal_id", proposal.id.to_string()), + ]; + assert_eq!(expected_attributes, res_ok.attributes); + let updated_prop = PROPOSALS.load(deps.as_mut().storage, 10).unwrap(); + assert_eq!(ProposalStatus::Overruled, updated_prop.status); +} + +#[test] +fn execute_update_config() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info("neutron1unknownsender", &[]); + + let msg = ExecuteMsg::UpdateConfig { + owner: None, + timelock_duration: Some(20), + }; + + let res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); + assert_eq!( + "cwd_subdao_timelock_single::state::Config not found", + res.unwrap_err().to_string() + ); + + let config = Config { + owner: Some(Addr::unchecked("owner")), + timelock_duration: 10, + subdao: Addr::unchecked(MOCK_SUBDAO_CORE_ADDR), + }; + CONFIG.save(deps.as_mut().storage, &config).unwrap(); + let res = execute(deps.as_mut(), env.clone(), info, msg.clone()); + assert_eq!("Unauthorized", res.unwrap_err().to_string()); + + let info = mock_info("owner", &[]); + let config = Config { + owner: None, + timelock_duration: 10, + subdao: Addr::unchecked(MOCK_SUBDAO_CORE_ADDR), + }; + CONFIG.save(deps.as_mut().storage, &config).unwrap(); + let res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); + assert_eq!("Unauthorized", res.unwrap_err().to_string()); + + let config = Config { + owner: Some(Addr::unchecked("owner")), + timelock_duration: 10, + subdao: Addr::unchecked(MOCK_SUBDAO_CORE_ADDR), + }; + CONFIG.save(deps.as_mut().storage, &config).unwrap(); + let res_ok = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + assert_eq!(0, res_ok.messages.len()); + let expected_attributes = vec![ + Attribute::new("action", "update_config"), + Attribute::new("owner", "owner"), + Attribute::new("timelock_duration", "20"), + ]; + assert_eq!(expected_attributes, res_ok.attributes); + let updated_config = CONFIG.load(deps.as_mut().storage).unwrap(); + assert_eq!( + updated_config, + Config { + owner: Some(Addr::unchecked("owner")), + timelock_duration: 20, + subdao: Addr::unchecked(MOCK_SUBDAO_CORE_ADDR) + } + ); + + let msg = ExecuteMsg::UpdateConfig { + owner: Some("neutron1newowner".to_string()), + timelock_duration: None, + }; + + let res_ok = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).unwrap(); + assert_eq!(0, res_ok.messages.len()); + let expected_attributes = vec![ + Attribute::new("action", "update_config"), + Attribute::new("owner", "neutron1newowner"), + Attribute::new("timelock_duration", "20"), + ]; + assert_eq!(expected_attributes, res_ok.attributes); + let updated_config = CONFIG.load(deps.as_mut().storage).unwrap(); + assert_eq!( + updated_config, + Config { + owner: Some(Addr::unchecked("neutron1newowner")), + timelock_duration: 20, + subdao: Addr::unchecked(MOCK_SUBDAO_CORE_ADDR) + } + ); + + // old owner + let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); + assert_eq!("Unauthorized", err.to_string()); +} + +#[test] +fn test_query_config() { + let mut deps = mock_dependencies(); + let config = Config { + owner: Some(Addr::unchecked("owner")), + timelock_duration: 20, + subdao: Addr::unchecked(MOCK_SUBDAO_CORE_ADDR), + }; + CONFIG.save(deps.as_mut().storage, &config).unwrap(); + let query_msg = QueryMsg::Config {}; + let res = query(deps.as_ref(), mock_env(), query_msg).unwrap(); + let queried_config: Config = from_binary(&res).unwrap(); + assert_eq!(config, queried_config) +} + +#[test] +fn test_query_proposals() { + let mut deps = mock_dependencies(); + for i in 1..=100 { + let prop = SingleChoiceProposal { + id: i, + timelock_ts: mock_env().block.time, + msgs: vec![NeutronMsg::remove_interchain_query(i).into()], + status: ProposalStatus::Timelocked, + }; + PROPOSALS.save(deps.as_mut().storage, i, &prop).unwrap(); + } + for i in 1..=100 { + let query_msg = QueryMsg::Proposal { proposal_id: i }; + let res = query(deps.as_ref(), mock_env(), query_msg).unwrap(); + let queried_prop: SingleChoiceProposal = from_binary(&res).unwrap(); + let expected_prop = SingleChoiceProposal { + id: i, + timelock_ts: mock_env().block.time, + msgs: vec![NeutronMsg::remove_interchain_query(i).into()], + status: ProposalStatus::Timelocked, + }; + assert_eq!(expected_prop, queried_prop) + } + + let query_msg = QueryMsg::ListProposals { + start_after: None, + limit: None, + }; + let res = query(deps.as_ref(), mock_env(), query_msg).unwrap(); + let queried_props: ProposalListResponse = from_binary(&res).unwrap(); + for (p, i) in queried_props.proposals.iter().zip(1..=DEFAULT_LIMIT) { + let expected_prop = SingleChoiceProposal { + id: i, + timelock_ts: mock_env().block.time, + msgs: vec![NeutronMsg::remove_interchain_query(i).into()], + status: ProposalStatus::Timelocked, + }; + assert_eq!(expected_prop, *p); + } + + let query_msg = QueryMsg::ListProposals { + start_after: None, + limit: Some(100), + }; + let res = query(deps.as_ref(), mock_env(), query_msg).unwrap(); + let queried_props: ProposalListResponse = from_binary(&res).unwrap(); + for (p, i) in queried_props.proposals.iter().zip(1..=DEFAULT_LIMIT) { + let expected_prop = SingleChoiceProposal { + id: i, + timelock_ts: mock_env().block.time, + msgs: vec![NeutronMsg::remove_interchain_query(i).into()], + status: ProposalStatus::Timelocked, + }; + assert_eq!(expected_prop, *p); + } + + let query_msg = QueryMsg::ListProposals { + start_after: None, + limit: Some(10), + }; + let res = query(deps.as_ref(), mock_env(), query_msg).unwrap(); + let queried_props: ProposalListResponse = from_binary(&res).unwrap(); + for (p, i) in queried_props.proposals.iter().zip(1..=10) { + let expected_prop = SingleChoiceProposal { + id: i, + timelock_ts: mock_env().block.time, + msgs: vec![NeutronMsg::remove_interchain_query(i).into()], + status: ProposalStatus::Timelocked, + }; + assert_eq!(expected_prop, *p); + } + + let query_msg = QueryMsg::ListProposals { + start_after: Some(50), + limit: None, + }; + let res = query(deps.as_ref(), mock_env(), query_msg).unwrap(); + let queried_props: ProposalListResponse = from_binary(&res).unwrap(); + for (p, i) in queried_props.proposals.iter().zip(51..=DEFAULT_LIMIT + 50) { + let expected_prop = SingleChoiceProposal { + id: i, + timelock_ts: mock_env().block.time, + msgs: vec![NeutronMsg::remove_interchain_query(i).into()], + status: ProposalStatus::Timelocked, + }; + assert_eq!(expected_prop, *p); + } + + let query_msg = QueryMsg::ListProposals { + start_after: Some(90), + limit: None, + }; + let res = query(deps.as_ref(), mock_env(), query_msg).unwrap(); + let queried_props: ProposalListResponse = from_binary(&res).unwrap(); + for (p, i) in queried_props.proposals.iter().zip(91..=100) { + let expected_prop = SingleChoiceProposal { + id: i, + timelock_ts: mock_env().block.time, + msgs: vec![NeutronMsg::remove_interchain_query(i).into()], + status: ProposalStatus::Timelocked, + }; + assert_eq!(expected_prop, *p); + } +} + +#[test] +fn test_reply() { + let mut deps = mock_dependencies(); + let msg = Reply { + id: 10, + result: SubMsgResult::Err("error".to_string()), + }; + let err = reply(deps.as_mut(), mock_env(), msg.clone()).unwrap_err(); + assert_eq!("no such proposal (10)", err.to_string()); + + let prop = SingleChoiceProposal { + id: 10, + timelock_ts: mock_env().block.time, + msgs: vec![NeutronMsg::remove_interchain_query(1).into()], + status: ProposalStatus::Timelocked, + }; + PROPOSALS.save(deps.as_mut().storage, 10, &prop).unwrap(); + let res_ok = reply(deps.as_mut(), mock_env(), msg).unwrap(); + assert_eq!(0, res_ok.messages.len()); + let expected_attributes = vec![Attribute::new("timelocked_proposal_execution_failed", "10")]; + assert_eq!(expected_attributes, res_ok.attributes); +} diff --git a/contracts/subdaos/cwd-subdao-timelock-single/src/tests.rs b/contracts/subdaos/cwd-subdao-timelock-single/src/tests.rs deleted file mode 100644 index 2be62303..00000000 --- a/contracts/subdaos/cwd-subdao-timelock-single/src/tests.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[test] -fn test_unbond_none_bonded() {} diff --git a/contracts/subdaos/voting/dao-voting-cw4/.cargo/config b/contracts/subdaos/voting/dao-voting-cw4/.cargo/config deleted file mode 100644 index 336b618a..00000000 --- a/contracts/subdaos/voting/dao-voting-cw4/.cargo/config +++ /dev/null @@ -1,4 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -unit-test = "test --lib" -schema = "run --example schema" diff --git a/contracts/subdaos/voting/dao-voting-cw4/.gitignore b/contracts/subdaos/voting/dao-voting-cw4/.gitignore deleted file mode 100644 index dfdaaa6b..00000000 --- a/contracts/subdaos/voting/dao-voting-cw4/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -# Build results -/target - -# Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) -.cargo-ok - -# Text file backups -**/*.rs.bk - -# macOS -.DS_Store - -# IDEs -*.iml -.idea diff --git a/contracts/subdaos/voting/dao-voting-cw4/Cargo.toml b/contracts/subdaos/voting/dao-voting-cw4/Cargo.toml deleted file mode 100644 index f478ce3b..00000000 --- a/contracts/subdaos/voting/dao-voting-cw4/Cargo.toml +++ /dev/null @@ -1,47 +0,0 @@ -[package] -name = "cw4-voting" -version = "0.1.0" -authors = ["Callum Anderson "] -edition = "2018" - -exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all instantiate/execute/query exports -library = [] - -[package.metadata.scripts] -optimize = """docker run --rm -v "$(pwd)":/code \ - --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ - --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/rust-optimizer:0.12.5 -""" - -[dependencies] -cosmwasm-std = { version = "1.0.0" } -cosmwasm-storage = { version = "1.0.0" } -cw-storage-plus = "0.13" -cw2 = "0.13" -cw-utils = "0.13" -schemars = "0.8" -serde = { version = "1.0", default-features = false, features = ["derive"] } -thiserror = { version = "1.0" } -cwd-macros = { path = "../../../../packages/cwd-macros" } -cwd-interface = {path = "../../../../packages/cwd-interface" } -cw4 = "0.13" -cw4-group = "0.13" - -[dev-dependencies] -cosmwasm-schema = { version = "1.0.0" } -cw-multi-test = "0.13" diff --git a/contracts/subdaos/voting/dao-voting-cw4/README.md b/contracts/subdaos/voting/dao-voting-cw4/README.md deleted file mode 100644 index 14793999..00000000 --- a/contracts/subdaos/voting/dao-voting-cw4/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# CW4 Group Voting - -A simple voting power module which determines voting power based on -the weight of a user in a cw4-group contract. This allocates voting -power in the same way that one would expect a multisig to. - -This contract implements the interface needed to be a DAO -DAO [voting -module](https://github.com/DA0-DA0/dao-contracts/wiki/DAO-DAO-Contracts-Design#the-voting-module). -For more information about how these modules fit together see -[this](https://github.com/DA0-DA0/dao-contracts/wiki/DAO-DAO-Contracts-Design) -wiki page. - -## Receiving updates - -This contract does not make subqueries to the cw4-group contract to -get an addresses voting power. Instead, it listens for -`MemberChangedHook` messages from said contract and caches voting -power locally. - -As the DAO is the admin of the underlying cw4-group contract it is -important that the DAO does not remove this contract from that -contract's list of hook receivers. Doing so will cause this contract -to stop receiving voting power updates. diff --git a/contracts/subdaos/voting/dao-voting-cw4/examples/schema.rs b/contracts/subdaos/voting/dao-voting-cw4/examples/schema.rs deleted file mode 100644 index 37457266..00000000 --- a/contracts/subdaos/voting/dao-voting-cw4/examples/schema.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::env::current_dir; -use std::fs::create_dir_all; - -use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; -use cosmwasm_std::Addr; -use cw4::MemberDiff; -use cw4_voting::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; -use cwd_interface::voting::{ - InfoResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, -}; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InstantiateMsg), &out_dir); - export_schema(&schema_for!(ExecuteMsg), &out_dir); - export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema(&schema_for!(MigrateMsg), &out_dir); - - export_schema(&schema_for!(MemberDiff), &out_dir); - - export_schema(&schema_for!(InfoResponse), &out_dir); - export_schema(&schema_for!(TotalPowerAtHeightResponse), &out_dir); - export_schema(&schema_for!(VotingPowerAtHeightResponse), &out_dir); - - // Auto TS code generation expects the query return type as QueryNameResponse - // Here we map query resonses to the correct name - export_schema_with_title(&schema_for!(Addr), &out_dir, "DaoResponse"); - export_schema_with_title(&schema_for!(Addr), &out_dir, "GroupContractResponse"); -} diff --git a/contracts/subdaos/voting/dao-voting-cw4/schema/dao-voting-cw4.json b/contracts/subdaos/voting/dao-voting-cw4/schema/dao-voting-cw4.json deleted file mode 100644 index cf6be8f6..00000000 --- a/contracts/subdaos/voting/dao-voting-cw4/schema/dao-voting-cw4.json +++ /dev/null @@ -1,318 +0,0 @@ -{ - "contract_name": "dao-voting-cw4", - "contract_version": "2.0.0-beta", - "idl_version": "1.0.0", - "instantiate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "cw4_group_code_id", - "initial_members" - ], - "properties": { - "cw4_group_code_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "initial_members": { - "type": "array", - "items": { - "$ref": "#/definitions/Member" - } - } - }, - "additionalProperties": false, - "definitions": { - "Member": { - "description": "A group member has a weight associated with them. This may all be equal, or may have meaning in the app that makes use of the group (eg. voting power)", - "type": "object", - "required": [ - "addr", - "weight" - ], - "properties": { - "addr": { - "type": "string" - }, - "weight": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - } - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "member_changed_hook" - ], - "properties": { - "member_changed_hook": { - "type": "object", - "required": [ - "diffs" - ], - "properties": { - "diffs": { - "type": "array", - "items": { - "$ref": "#/definitions/MemberDiff" - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "MemberDiff": { - "description": "MemberDiff shows the old and new states for a given cw4 member They cannot both be None. old = None, new = Some -> Insert old = Some, new = Some -> Update old = Some, new = None -> Delete", - "type": "object", - "required": [ - "key" - ], - "properties": { - "key": { - "type": "string" - }, - "new": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "old": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - } - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "group_contract" - ], - "properties": { - "group_contract": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns the voting power for an address at a given height.", - "type": "object", - "required": [ - "voting_power_at_height" - ], - "properties": { - "voting_power_at_height": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - }, - "height": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns the total voting power at a given block heigh.", - "type": "object", - "required": [ - "total_power_at_height" - ], - "properties": { - "total_power_at_height": { - "type": "object", - "properties": { - "height": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns the address of the DAO this module belongs to.", - "type": "object", - "required": [ - "dao" - ], - "properties": { - "dao": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns contract version info.", - "type": "object", - "required": [ - "info" - ], - "properties": { - "info": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "migrate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MigrateMsg", - "type": "object", - "additionalProperties": false - }, - "sudo": null, - "responses": { - "dao": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Addr", - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "group_contract": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Addr", - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "info": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InfoResponse", - "type": "object", - "required": [ - "info" - ], - "properties": { - "info": { - "$ref": "#/definitions/ContractVersion" - } - }, - "additionalProperties": false, - "definitions": { - "ContractVersion": { - "type": "object", - "required": [ - "contract", - "version" - ], - "properties": { - "contract": { - "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", - "type": "string" - }, - "version": { - "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", - "type": "string" - } - }, - "additionalProperties": false - } - } - }, - "total_power_at_height": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TotalPowerAtHeightResponse", - "type": "object", - "required": [ - "height", - "power" - ], - "properties": { - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "power": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "voting_power_at_height": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VotingPowerAtHeightResponse", - "type": "object", - "required": [ - "height", - "power" - ], - "properties": { - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "power": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - } - } -} diff --git a/contracts/subdaos/voting/dao-voting-cw4/src/contract.rs b/contracts/subdaos/voting/dao-voting-cw4/src/contract.rs deleted file mode 100644 index 9e215f70..00000000 --- a/contracts/subdaos/voting/dao-voting-cw4/src/contract.rs +++ /dev/null @@ -1,211 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdError, StdResult, - SubMsg, Uint128, WasmMsg, -}; -use cw2::set_contract_version; -use cw_utils::parse_reply_instantiate_data; -use cwd_interface::voting; - -use crate::error::ContractError; -use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; -use crate::state::{DAO, GROUP_CONTRACT, TOTAL_WEIGHT, USER_WEIGHTS}; - -pub(crate) const CONTRACT_NAME: &str = "crates.io:cw4-voting"; -pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -const INSTANTIATE_GROUP_REPLY_ID: u64 = 0; - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> Result { - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - if msg.initial_members.is_empty() { - return Err(ContractError::NoMembers {}); - } - - let mut total_weight = Uint128::zero(); - for member in msg.initial_members.iter() { - let member_addr = deps.api.addr_validate(&member.addr)?; - let weight = Uint128::from(member.weight); - USER_WEIGHTS.save(deps.storage, &member_addr, &weight, env.block.height)?; - total_weight += weight; - } - - if total_weight.is_zero() { - return Err(ContractError::ZeroTotalWeight {}); - } - TOTAL_WEIGHT.save(deps.storage, &total_weight, env.block.height)?; - - // We need to set ourself as the CW4 admin it is then transferred to the DAO in the reply - let msg = WasmMsg::Instantiate { - admin: Some(info.sender.to_string()), - code_id: msg.cw4_group_code_id, - msg: to_binary(&cw4_group::msg::InstantiateMsg { - admin: Some(env.contract.address.to_string()), - members: msg.initial_members, - })?, - funds: vec![], - label: env.contract.address.to_string(), - }; - - let msg = SubMsg::reply_on_success(msg, INSTANTIATE_GROUP_REPLY_ID); - - DAO.save(deps.storage, &info.sender)?; - - Ok(Response::new() - .add_attribute("action", "instantiate") - .add_submessage(msg)) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::MemberChangedHook { diffs } => { - execute_member_changed_hook(deps, env, info, diffs) - } - } -} - -pub fn execute_member_changed_hook( - deps: DepsMut, - env: Env, - info: MessageInfo, - diffs: Vec, -) -> Result { - let group_contract = GROUP_CONTRACT.load(deps.storage)?; - if info.sender != group_contract { - return Err(ContractError::Unauthorized {}); - } - - let total_weight = TOTAL_WEIGHT.load(deps.storage)?; - // As difference can be negative we need to keep track of both - // In seperate counters to apply at once and prevent underflow - let mut positive_difference: Uint128 = Uint128::zero(); - let mut negative_difference: Uint128 = Uint128::zero(); - for diff in diffs { - let user_address = deps.api.addr_validate(&diff.key)?; - let weight = diff.new.unwrap_or_default(); - let old = diff.old.unwrap_or_default(); - // Do we need to add to positive difference or negative difference - if weight > old { - positive_difference += Uint128::from(weight - old); - } else { - negative_difference += Uint128::from(old - weight); - } - USER_WEIGHTS.save( - deps.storage, - &user_address, - &Uint128::from(weight), - env.block.height, - )?; - } - let new_total_weight = total_weight - .checked_add(positive_difference) - .map_err(StdError::overflow)? - .checked_sub(negative_difference) - .map_err(StdError::overflow)?; - TOTAL_WEIGHT.save(deps.storage, &new_total_weight, env.block.height)?; - - Ok(Response::new() - .add_attribute("action", "member_changed_hook") - .add_attribute("total_weight", new_total_weight.to_string())) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::VotingPowerAtHeight { address, height } => { - query_voting_power_at_height(deps, env, address, height) - } - QueryMsg::TotalPowerAtHeight { height } => query_total_power_at_height(deps, env, height), - QueryMsg::Info {} => query_info(deps), - QueryMsg::GroupContract {} => to_binary(&GROUP_CONTRACT.load(deps.storage)?), - QueryMsg::Dao {} => to_binary(&DAO.load(deps.storage)?), - } -} - -pub fn query_voting_power_at_height( - deps: Deps, - env: Env, - address: String, - height: Option, -) -> StdResult { - let address = deps.api.addr_validate(&address)?; - let height = height.unwrap_or(env.block.height); - let power = USER_WEIGHTS - .may_load_at_height(deps.storage, &address, height)? - .unwrap_or_default(); - - to_binary(&voting::VotingPowerAtHeightResponse { power, height }) -} - -pub fn query_total_power_at_height(deps: Deps, env: Env, height: Option) -> StdResult { - let height = height.unwrap_or(env.block.height); - let power = TOTAL_WEIGHT - .may_load_at_height(deps.storage, height)? - .unwrap_or_default(); - to_binary(&voting::TotalPowerAtHeightResponse { power, height }) -} - -pub fn query_info(deps: Deps) -> StdResult { - let info = cw2::get_contract_version(deps.storage)?; - to_binary(&voting::InfoResponse { info }) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - // Don't do any state migrations. - Ok(Response::default()) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { - match msg.id { - INSTANTIATE_GROUP_REPLY_ID => { - let res = parse_reply_instantiate_data(msg); - match res { - Ok(res) => { - let group_contract = GROUP_CONTRACT.may_load(deps.storage)?; - if group_contract.is_some() { - return Err(ContractError::DuplicateGroupContract {}); - } - let group_contract = deps.api.addr_validate(&res.contract_address)?; - let dao_address = DAO.load(deps.storage)?; - GROUP_CONTRACT.save(deps.storage, &group_contract)?; - let msg1 = WasmMsg::Execute { - contract_addr: group_contract.to_string(), - msg: to_binary(&cw4_group::msg::ExecuteMsg::AddHook { - addr: env.contract.address.to_string(), - })?, - funds: vec![], - }; - // Transfer admin status to the DAO - let msg2 = WasmMsg::Execute { - contract_addr: group_contract.to_string(), - msg: to_binary(&cw4_group::msg::ExecuteMsg::UpdateAdmin { - admin: Some(dao_address.to_string()), - })?, - funds: vec![], - }; - Ok(Response::default() - .add_attribute("group_contract_address", group_contract) - .add_message(msg1) - .add_message(msg2)) - } - Err(_) => Err(ContractError::GroupContractInstantiateError {}), - } - } - _ => Err(ContractError::UnknownReplyId { id: msg.id }), - } -} diff --git a/contracts/subdaos/voting/dao-voting-cw4/src/error.rs b/contracts/subdaos/voting/dao-voting-cw4/src/error.rs deleted file mode 100644 index a8bd1a90..00000000 --- a/contracts/subdaos/voting/dao-voting-cw4/src/error.rs +++ /dev/null @@ -1,29 +0,0 @@ -use cosmwasm_std::StdError; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("Unauthorized")] - Unauthorized {}, - - #[error("Can not change the contract's token after it has been set")] - DuplicateGroupContract {}, - - #[error("Error occured whilst instantiating group contract")] - GroupContractInstantiateError {}, - - #[error("Cannot instantiate a group contract with no initial members")] - NoMembers {}, - - #[error("Cannot instantiate a group contract with duplicate initial members")] - DuplicateMembers {}, - - #[error("Total weight of the CW4 contract cannot be zero")] - ZeroTotalWeight {}, - - #[error("Got a submessage reply with unknown id: {id}")] - UnknownReplyId { id: u64 }, -} diff --git a/contracts/subdaos/voting/dao-voting-cw4/src/lib.rs b/contracts/subdaos/voting/dao-voting-cw4/src/lib.rs deleted file mode 100644 index d1800adb..00000000 --- a/contracts/subdaos/voting/dao-voting-cw4/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] - -pub mod contract; -mod error; -pub mod msg; -pub mod state; - -#[cfg(test)] -mod tests; - -pub use crate::error::ContractError; diff --git a/contracts/subdaos/voting/dao-voting-cw4/src/msg.rs b/contracts/subdaos/voting/dao-voting-cw4/src/msg.rs deleted file mode 100644 index 3e07b47f..00000000 --- a/contracts/subdaos/voting/dao-voting-cw4/src/msg.rs +++ /dev/null @@ -1,27 +0,0 @@ -use cwd_macros::{info_query, voting_query}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InstantiateMsg { - pub cw4_group_code_id: u64, - pub initial_members: Vec, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ExecuteMsg { - MemberChangedHook { diffs: Vec }, -} - -#[voting_query] -#[info_query] -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - GroupContract {}, - Dao {}, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct MigrateMsg {} diff --git a/contracts/subdaos/voting/dao-voting-cw4/src/state.rs b/contracts/subdaos/voting/dao-voting-cw4/src/state.rs deleted file mode 100644 index 11b44972..00000000 --- a/contracts/subdaos/voting/dao-voting-cw4/src/state.rs +++ /dev/null @@ -1,19 +0,0 @@ -use cosmwasm_std::{Addr, Uint128}; -use cw_storage_plus::{Item, SnapshotItem, SnapshotMap, Strategy}; - -pub const USER_WEIGHTS: SnapshotMap<&Addr, Uint128> = SnapshotMap::new( - "user_weights", - "user_weights__checkpoints", - "user_weights__changelog", - Strategy::EveryBlock, -); - -pub const TOTAL_WEIGHT: SnapshotItem = SnapshotItem::new( - "total_weight", - "total_weight__checkpoints", - "total_weight__changelog", - Strategy::EveryBlock, -); - -pub const GROUP_CONTRACT: Item = Item::new("group_contract"); -pub const DAO: Item = Item::new("dao_address"); diff --git a/contracts/subdaos/voting/dao-voting-cw4/src/tests.rs b/contracts/subdaos/voting/dao-voting-cw4/src/tests.rs deleted file mode 100644 index d02a4a24..00000000 --- a/contracts/subdaos/voting/dao-voting-cw4/src/tests.rs +++ /dev/null @@ -1,681 +0,0 @@ -use cosmwasm_std::{ - testing::{mock_dependencies, mock_env}, - to_binary, Addr, CosmosMsg, Empty, Uint128, WasmMsg, -}; -use cw2::ContractVersion; -use cw_multi_test::{next_block, App, Contract, ContractWrapper, Executor}; - -use cwd_interface::voting::{ - InfoResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, -}; - -use crate::{ - contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}, - msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, - ContractError, -}; - -const DAO_ADDR: &str = "dao"; -const ADDR1: &str = "addr1"; -const ADDR2: &str = "addr2"; -const ADDR3: &str = "addr3"; -const ADDR4: &str = "addr4"; - -fn cw4_contract() -> Box> { - let contract = ContractWrapper::new( - cw4_group::contract::execute, - cw4_group::contract::instantiate, - cw4_group::contract::query, - ); - Box::new(contract) -} - -fn voting_contract() -> Box> { - let contract = ContractWrapper::new( - crate::contract::execute, - crate::contract::instantiate, - crate::contract::query, - ) - .with_reply(crate::contract::reply) - .with_migrate(crate::contract::migrate); - Box::new(contract) -} - -fn instantiate_voting(app: &mut App, voting_id: u64, msg: InstantiateMsg) -> Addr { - app.instantiate_contract( - voting_id, - Addr::unchecked(DAO_ADDR), - &msg, - &[], - "voting module", - None, - ) - .unwrap() -} - -fn setup_test_case(app: &mut App) -> Addr { - let cw4_id = app.store_code(cw4_contract()); - let voting_id = app.store_code(voting_contract()); - - let members = vec![ - cw4::Member { - addr: ADDR1.to_string(), - weight: 1, - }, - cw4::Member { - addr: ADDR2.to_string(), - weight: 1, - }, - cw4::Member { - addr: ADDR3.to_string(), - weight: 1, - }, - cw4::Member { - addr: ADDR4.to_string(), - weight: 0, - }, - ]; - instantiate_voting( - app, - voting_id, - InstantiateMsg { - cw4_group_code_id: cw4_id, - initial_members: members, - }, - ) -} - -#[test] -fn test_instantiate() { - let mut app = App::default(); - // Valid instantiate no panics - let _voting_addr = setup_test_case(&mut app); - - // Instantiate with no members, error - let voting_id = app.store_code(voting_contract()); - let cw4_id = app.store_code(cw4_contract()); - let msg = InstantiateMsg { - cw4_group_code_id: cw4_id, - initial_members: vec![], - }; - let _err = app - .instantiate_contract( - voting_id, - Addr::unchecked(DAO_ADDR), - &msg, - &[], - "voting module", - None, - ) - .unwrap_err(); - - // Instantiate with members but no weight - let msg = InstantiateMsg { - cw4_group_code_id: cw4_id, - initial_members: vec![ - cw4::Member { - addr: ADDR1.to_string(), - weight: 0, - }, - cw4::Member { - addr: ADDR2.to_string(), - weight: 0, - }, - cw4::Member { - addr: ADDR3.to_string(), - weight: 0, - }, - ], - }; - let _err = app - .instantiate_contract( - voting_id, - Addr::unchecked(DAO_ADDR), - &msg, - &[], - "voting module", - None, - ) - .unwrap_err(); -} - -#[test] -fn test_contract_info() { - let mut app = App::default(); - let voting_addr = setup_test_case(&mut app); - - let info: InfoResponse = app - .wrap() - .query_wasm_smart(voting_addr.clone(), &QueryMsg::Info {}) - .unwrap(); - assert_eq!( - info, - InfoResponse { - info: ContractVersion { - contract: "crates.io:dao-voting-cw4".to_string(), - version: env!("CARGO_PKG_VERSION").to_string() - } - } - ); - - // Ensure group contract is set - let _group_contract: Addr = app - .wrap() - .query_wasm_smart(voting_addr.clone(), &QueryMsg::GroupContract {}) - .unwrap(); - - let dao_contract: Addr = app - .wrap() - .query_wasm_smart(voting_addr, &QueryMsg::Dao {}) - .unwrap(); - assert_eq!(dao_contract, Addr::unchecked(DAO_ADDR)); -} - -#[test] -fn test_permissions() { - let mut app = App::default(); - let voting_addr = setup_test_case(&mut app); - - // DAO can not execute hook message. - let err: ContractError = app - .execute_contract( - Addr::unchecked(DAO_ADDR), - voting_addr.clone(), - &ExecuteMsg::MemberChangedHook { diffs: vec![] }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!(err, ContractError::Unauthorized {})); - - // Contract itself can not execute hook message. - let err: ContractError = app - .execute_contract( - voting_addr.clone(), - voting_addr, - &ExecuteMsg::MemberChangedHook { diffs: vec![] }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!(err, ContractError::Unauthorized {})); -} - -#[test] -fn test_power_at_height() { - let mut app = App::default(); - let voting_addr = setup_test_case(&mut app); - app.update_block(next_block); - - let cw4_addr: Addr = app - .wrap() - .query_wasm_smart(voting_addr.clone(), &QueryMsg::GroupContract {}) - .unwrap(); - - let addr1_voting_power: VotingPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr.clone(), - &QueryMsg::VotingPowerAtHeight { - address: ADDR1.to_string(), - height: None, - }, - ) - .unwrap(); - assert_eq!(addr1_voting_power.power, Uint128::new(1u128)); - assert_eq!(addr1_voting_power.height, app.block_info().height); - - let total_voting_power: TotalPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr.clone(), - &QueryMsg::TotalPowerAtHeight { height: None }, - ) - .unwrap(); - assert_eq!(total_voting_power.power, Uint128::new(3u128)); - assert_eq!(total_voting_power.height, app.block_info().height); - - // Update ADDR1's weight to 2 - let msg = cw4_group::msg::ExecuteMsg::UpdateMembers { - remove: vec![], - add: vec![cw4::Member { - addr: ADDR1.to_string(), - weight: 2, - }], - }; - - // Should still be one as voting power should not update until - // the following block. - let addr1_voting_power: VotingPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr.clone(), - &QueryMsg::VotingPowerAtHeight { - address: ADDR1.to_string(), - height: None, - }, - ) - .unwrap(); - assert_eq!(addr1_voting_power.power, Uint128::new(1u128)); - - // Same should be true about the groups contract. - let cw4_power: cw4::MemberResponse = app - .wrap() - .query_wasm_smart( - cw4_addr.clone(), - &cw4::Cw4QueryMsg::Member { - addr: ADDR1.to_string(), - at_height: None, - }, - ) - .unwrap(); - assert_eq!(cw4_power.weight.unwrap(), 1); - - app.execute_contract(Addr::unchecked(DAO_ADDR), cw4_addr.clone(), &msg, &[]) - .unwrap(); - app.update_block(next_block); - - // Should now be 2 - let addr1_voting_power: VotingPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr.clone(), - &QueryMsg::VotingPowerAtHeight { - address: ADDR1.to_string(), - height: None, - }, - ) - .unwrap(); - assert_eq!(addr1_voting_power.power, Uint128::new(2u128)); - assert_eq!(addr1_voting_power.height, app.block_info().height); - - // Check we can still get the 1 weight he had last block - let addr1_voting_power: VotingPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr.clone(), - &QueryMsg::VotingPowerAtHeight { - address: ADDR1.to_string(), - height: Some(app.block_info().height - 1), - }, - ) - .unwrap(); - assert_eq!(addr1_voting_power.power, Uint128::new(1u128)); - assert_eq!(addr1_voting_power.height, app.block_info().height - 1); - - // Check total power is now 4 - let total_voting_power: TotalPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr.clone(), - &QueryMsg::TotalPowerAtHeight { height: None }, - ) - .unwrap(); - assert_eq!(total_voting_power.power, Uint128::new(4u128)); - assert_eq!(total_voting_power.height, app.block_info().height); - - // Check total power for last block is 3 - let total_voting_power: TotalPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr.clone(), - &QueryMsg::TotalPowerAtHeight { - height: Some(app.block_info().height - 1), - }, - ) - .unwrap(); - assert_eq!(total_voting_power.power, Uint128::new(3u128)); - assert_eq!(total_voting_power.height, app.block_info().height - 1); - - // Update ADDR1's weight back to 1 - let msg = cw4_group::msg::ExecuteMsg::UpdateMembers { - remove: vec![], - add: vec![cw4::Member { - addr: ADDR1.to_string(), - weight: 1, - }], - }; - - app.execute_contract(Addr::unchecked(DAO_ADDR), cw4_addr.clone(), &msg, &[]) - .unwrap(); - app.update_block(next_block); - - // Should now be 1 again - let addr1_voting_power: VotingPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr.clone(), - &QueryMsg::VotingPowerAtHeight { - address: ADDR1.to_string(), - height: None, - }, - ) - .unwrap(); - assert_eq!(addr1_voting_power.power, Uint128::new(1u128)); - assert_eq!(addr1_voting_power.height, app.block_info().height); - - // Check total power for current block is now 3 - let total_voting_power: TotalPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr.clone(), - &QueryMsg::TotalPowerAtHeight { height: None }, - ) - .unwrap(); - assert_eq!(total_voting_power.power, Uint128::new(3u128)); - assert_eq!(total_voting_power.height, app.block_info().height); - - // Check total power for last block is 4 - let total_voting_power: TotalPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr.clone(), - &QueryMsg::TotalPowerAtHeight { - height: Some(app.block_info().height - 1), - }, - ) - .unwrap(); - assert_eq!(total_voting_power.power, Uint128::new(4u128)); - assert_eq!(total_voting_power.height, app.block_info().height - 1); - - // Remove address 2 completely - let msg = cw4_group::msg::ExecuteMsg::UpdateMembers { - remove: vec![ADDR2.to_string()], - add: vec![], - }; - - app.execute_contract(Addr::unchecked(DAO_ADDR), cw4_addr.clone(), &msg, &[]) - .unwrap(); - app.update_block(next_block); - - // ADDR2 power is now 0 - let addr2_voting_power: VotingPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr.clone(), - &QueryMsg::VotingPowerAtHeight { - address: ADDR2.to_string(), - height: None, - }, - ) - .unwrap(); - assert_eq!(addr2_voting_power.power, Uint128::zero()); - assert_eq!(addr2_voting_power.height, app.block_info().height); - - // Check total power for current block is now 2 - let total_voting_power: TotalPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr.clone(), - &QueryMsg::TotalPowerAtHeight { height: None }, - ) - .unwrap(); - assert_eq!(total_voting_power.power, Uint128::new(2u128)); - assert_eq!(total_voting_power.height, app.block_info().height); - - // Check total power for last block is 3 - let total_voting_power: TotalPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr.clone(), - &QueryMsg::TotalPowerAtHeight { - height: Some(app.block_info().height - 1), - }, - ) - .unwrap(); - assert_eq!(total_voting_power.power, Uint128::new(3u128)); - assert_eq!(total_voting_power.height, app.block_info().height - 1); - - // Readd ADDR2 with 10 power - let msg = cw4_group::msg::ExecuteMsg::UpdateMembers { - remove: vec![], - add: vec![cw4::Member { - addr: ADDR2.to_string(), - weight: 10, - }], - }; - - app.execute_contract(Addr::unchecked(DAO_ADDR), cw4_addr, &msg, &[]) - .unwrap(); - app.update_block(next_block); - - // ADDR2 power is now 10 - let addr2_voting_power: VotingPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr.clone(), - &QueryMsg::VotingPowerAtHeight { - address: ADDR2.to_string(), - height: None, - }, - ) - .unwrap(); - assert_eq!(addr2_voting_power.power, Uint128::new(10u128)); - assert_eq!(addr2_voting_power.height, app.block_info().height); - - // Check total power for current block is now 12 - let total_voting_power: TotalPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr.clone(), - &QueryMsg::TotalPowerAtHeight { height: None }, - ) - .unwrap(); - assert_eq!(total_voting_power.power, Uint128::new(12u128)); - assert_eq!(total_voting_power.height, app.block_info().height); - - // Check total power for last block is 2 - let total_voting_power: TotalPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr, - &QueryMsg::TotalPowerAtHeight { - height: Some(app.block_info().height - 1), - }, - ) - .unwrap(); - assert_eq!(total_voting_power.power, Uint128::new(2u128)); - assert_eq!(total_voting_power.height, app.block_info().height - 1); -} - -#[test] -fn test_migrate() { - let mut app = App::default(); - - let initial_members = vec![ - cw4::Member { - addr: ADDR1.to_string(), - weight: 1, - }, - cw4::Member { - addr: ADDR2.to_string(), - weight: 1, - }, - cw4::Member { - addr: ADDR3.to_string(), - weight: 1, - }, - ]; - - // Instantiate with no members, error - let voting_id = app.store_code(voting_contract()); - let cw4_id = app.store_code(cw4_contract()); - let msg = InstantiateMsg { - cw4_group_code_id: cw4_id, - initial_members, - }; - let voting_addr = app - .instantiate_contract( - voting_id, - Addr::unchecked(DAO_ADDR), - &msg, - &[], - "voting module", - Some(DAO_ADDR.to_string()), - ) - .unwrap(); - - let power: VotingPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr.clone(), - &QueryMsg::VotingPowerAtHeight { - address: ADDR1.to_string(), - height: None, - }, - ) - .unwrap(); - - app.execute( - Addr::unchecked(DAO_ADDR), - CosmosMsg::Wasm(WasmMsg::Migrate { - contract_addr: voting_addr.to_string(), - new_code_id: voting_id, - msg: to_binary(&MigrateMsg {}).unwrap(), - }), - ) - .unwrap(); - - let new_power: VotingPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr, - &QueryMsg::VotingPowerAtHeight { - address: ADDR1.to_string(), - height: None, - }, - ) - .unwrap(); - - assert_eq!(new_power, power) -} - -#[test] -fn test_duplicate_member() { - let mut app = App::default(); - let _voting_addr = setup_test_case(&mut app); - let voting_id = app.store_code(voting_contract()); - let cw4_id = app.store_code(cw4_contract()); - // Instantiate with members but have a duplicate - // Total weight is actually 69 but ADDR3 appears twice. - let msg = InstantiateMsg { - cw4_group_code_id: cw4_id, - initial_members: vec![ - cw4::Member { - addr: ADDR3.to_string(), // same address above - weight: 19, - }, - cw4::Member { - addr: ADDR1.to_string(), - weight: 25, - }, - cw4::Member { - addr: ADDR2.to_string(), - weight: 25, - }, - cw4::Member { - addr: ADDR3.to_string(), - weight: 19, - }, - ], - }; - // Previous versions voting power was 100, due to no dedup. - // Now we error - // Bug busted : ) - let _voting_addr = app - .instantiate_contract( - voting_id, - Addr::unchecked(DAO_ADDR), - &msg, - &[], - "voting module", - None, - ) - .unwrap_err(); -} - -#[test] -fn test_zero_voting_power() { - let mut app = App::default(); - let voting_addr = setup_test_case(&mut app); - app.update_block(next_block); - - let cw4_addr: Addr = app - .wrap() - .query_wasm_smart(voting_addr.clone(), &QueryMsg::GroupContract {}) - .unwrap(); - - // check that ADDR4 weight is 0 - let addr4_voting_power: VotingPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr.clone(), - &QueryMsg::VotingPowerAtHeight { - address: ADDR4.to_string(), - height: None, - }, - ) - .unwrap(); - assert_eq!(addr4_voting_power.power, Uint128::new(0)); - assert_eq!(addr4_voting_power.height, app.block_info().height); - - // Update ADDR1's weight to 0 - let msg = cw4_group::msg::ExecuteMsg::UpdateMembers { - remove: vec![], - add: vec![cw4::Member { - addr: ADDR1.to_string(), - weight: 0, - }], - }; - app.execute_contract(Addr::unchecked(DAO_ADDR), cw4_addr, &msg, &[]) - .unwrap(); - - // Should still be one as voting power should not update until - // the following block. - let addr1_voting_power: VotingPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr.clone(), - &QueryMsg::VotingPowerAtHeight { - address: ADDR1.to_string(), - height: None, - }, - ) - .unwrap(); - assert_eq!(addr1_voting_power.power, Uint128::new(1u128)); - - // update block to see the changes - app.update_block(next_block); - let addr1_voting_power: VotingPowerAtHeightResponse = app - .wrap() - .query_wasm_smart( - voting_addr.clone(), - &QueryMsg::VotingPowerAtHeight { - address: ADDR1.to_string(), - height: None, - }, - ) - .unwrap(); - assert_eq!(addr1_voting_power.power, Uint128::new(0u128)); - assert_eq!(addr1_voting_power.height, app.block_info().height); - - // Check total power is now 2 - let total_voting_power: TotalPowerAtHeightResponse = app - .wrap() - .query_wasm_smart(voting_addr, &QueryMsg::TotalPowerAtHeight { height: None }) - .unwrap(); - assert_eq!(total_voting_power.power, Uint128::new(2u128)); - assert_eq!(total_voting_power.height, app.block_info().height); -} - -#[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 {}).unwrap(); - let version = cw2::get_contract_version(&deps.storage).unwrap(); - assert_eq!(version.version, CONTRACT_VERSION); - assert_eq!(version.contract, CONTRACT_NAME); -} diff --git a/test_subdao_proposal.sh b/test_subdao_proposal.sh index 39f72a64..79df42dc 100644 --- a/test_subdao_proposal.sh +++ b/test_subdao_proposal.sh @@ -3,8 +3,8 @@ BIN=neutrond CORE_CONTRACT=./artifacts/cwd_subdao_core.wasm PROPOSAL_SINGLE_CONTRACT=./artifacts/cwd_subdao_proposal_single.wasm TIMELOCK_SINGLE_CONTRACT=./artifacts/cwd_subdao_timelock_single.wasm -CW4_VOTING_CONTRACT=./artifacts/cw4_voting.wasm -CW4_GROUP_CONTRACT=./artifacts/cw4_group.wasm +CW4_VOTING_CONTRACT=./artifacts/cw4_voting.wasm # Vanilla DAO DAO contract, compiled from original repo +CW4_GROUP_CONTRACT=./artifacts/cw4_group.wasm # Vanilla cw-plus contract, compiled from original repo PRE_PROPOSE_SINGLE_CONTRACT=./artifacts/cwd_subdao_pre_propose_single.wasm CHAIN_ID=test-1